How to render properties of objects in React? - redux

Where exactly should I deal with the problem of this component not loading with the desired state?
My render method causes the following error...
Uncaught TypeError: Cannot read property 'email' of undefined
...even though the JSON.stringify line shows me that the email property does (eventually) exist.
The console.log down in mapStateToProps confirms that state loads first without the any user property (thus causing the error).
Behold my naive attempt to resolve this in my constructor method. It's not working.
What is the right way to deal with this situation? Some conditional inside the render method? Tried that too but still no luck.
import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux';
import * as actions from '../actions';
class Feature extends Component {
constructor(props){
super(props);
this.state = {
'auth': {
'user':{
email:'',
id:''
}
}
}
}
componentWillMount() {
this.props.fetchMessage(); // puts the user object into state
}
render() {
return (
<div className="feature">
Here is your feature
{JSON.stringify(this.props.user , null, 2)}
{this.props.user.email}
</div>
);
}
}
function mapStateToProps(state) {
console.log('state',state);
return { user: state.auth.user }
}
export default connect(mapStateToProps, actions)(Feature);
/////////// action /////////
export function fetchMessage(){
return function(dispatch){
axios
.get(ROOT_URL, {
headers: {
authorization: localStorage.getItem('token')
}
})
.then((response) => {
dispatch({
type: FETCH_MESSAGE,
payload: response.data.user
})
})
}
}
///////////////// reducer /////////////
var authReducer = (state={}, action) => {
console.log('action.payload',action.payload);
switch(action.type){
case AUTH_USER: return {...state, error: '', authenticated: true};
case UNAUTH_USER: return {...state, error: '', authenticated: false};
case AUTH_ERROR: return {...state, error: action.payload};
case FETCH_MESSAGE: return {...state, user: {
email: action.payload.email,
id: action.payload._id
}};
default: return state;
};
};

So here is what is happening.. You are making a server request for a user object and on success the received object is stored in your redux store. While performing such an action your component is rendered as follows:
You have initiated the request to the server, which means currently there is no user in your store and so the component is rendered with this.props.user undefined.
Your request is successful and the user object is stored in your store. When this happens react-redux will re-render your component with the user object and this.props.user is available in your component.
During the first step since this.props.user is unavailable you are getting an error while accessing this.props.user.email. Although #jpdelatorre 's answer is pretty good, another thing you can do is simply add a check in your render method.
render() {
let user = this.props.user || {};
....
{user.email}
....
}

Redux uses a global state called store that lives outside your component.
Inside your constructor you have a this.state = {...} statement. This is a local state that is only available to the Feature component.
connect from react-redux basically connects the info inside the store to your component props hence called mapStateToProps.
Edit your mapStateToProps to something like...
function mapStateToProps(state) {
const { auth: { user = {} } = {}} = state;
return {
user
}
}
What it does is try to extract the user property from the store and if it doesn't exist yet, set it to empty object. Your error is due to your mapStateToProps accessing the user property that doesn't exist yet. You might want to set a default value as your initialState to avoid this issue.

Related

Why my Redux App return that [ Immer ] error?

I don't know. Why even I added my push function on my object to return my new result, The app is printing error on my console.log.
slice.js
import { createSlice } from '#reduxjs/toolkit';
import { pushProduct } from '../commons/push';
export const slice = createSlice({
name: 'initial',
initialState : {
product: [],
},
reducers: {
ADDS(state, actions) {
return {
...state,
product: pushProduct(state.product, actions.payload),
console1: console.log('State: ', state.product),
console2: console.log('Actions: ', actions.payload),
}
}
}
});
export const { ADDS } = slice.actions;
export default slice.reducer;
push.js
// Push new prpduct to the cart
export const pushProduct = (initial, productSelect) => { return initial.push(productSelect) };
console.log error
errors.ts:49 Uncaught Error: [Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft.
Thank You
Per the error message: Immer lets you update the state in two ways. One is "mutating" the existing state, and the other is returning a new value. But, you can only do one of those at a time.
You're trying to do both. You have return {...state}, but you also have pushProduct() which sounds like it's mutating.
The best answer here is to not try to do return {...state} at all, and just "mutate" the existing state.
See https://redux-toolkit.js.org/usage/immer-reducers#mutating-and-returning-state for more details.

React Redux Thunk Pass This.Props Methods in Action Creator

I am using react with react-stepzilla with Redux , Redux-thunk the problem is i want to use jumpToState(n) method inside action creator. but i am not able to access this method inside redux action creator file.
Action File
export const checkUser = (username) => {
return (dispatch,getState)=>{
wApi({user:username}).then(response => {
dispatch({
type: ActionTypes.CHECK_USER_NAME,
payload:response
})
e.jumpToStep(1);//Here it is Stepzilla Method
}).catch(err => {})
}
}
getState() method only providing me state value which i declared in reducer.
console.log(getState)
userdetail:{
username:"USER1001"
usertype:"SUPER"
isactive:"YES"
}
Reducer File
const defaultState={
userdetail:{
username:""
usertype:""
isactive:""
}
}
const reducer =(state=defaultState,action)=>{
switch (action.type) {
case ActionTypes.CHECK_USER_NAME :
{
return {
...state,
userdetail:action.payload,
}
}
default:
return state
}
}
export default reducer;
CheckUserName.js File Code
componentWillMount() {
this.props.checkUser("USER1001")
//console.log(this.props)
//{Here in console Output i can see "jumpToState" method in this.props}
//this.props.jumpToStep(1);
}
I find the solution by passing whole this.props to action creator method.
this.props.checkUser("USER1001",this.props)
i want to ask there is any alternate method for achieving this. i am new to react
From the documentation of react-stepzilla:
stepzilla injects an utility method called jumpToStep as a prop into all your react step components
As it is normal function in your props, you can pass it to your action creator as an argument and use it there. Passing the whole this.props is not necessary.
this.props.checkUser("USER1001", this.props.jumpToStep)
export const checkUser = (username, jumpToStep) => {
return (dispatch,getState)=>{
wApi({user:username}).then(response => {
dispatch({
type: ActionTypes.CHECK_USER_NAME,
payload:response
})
jumpToStep(1);//Here it is Stepzilla Method
}).catch(err => {})
}
}

BotFramework WebChat - Sample #14 - Middleware for both Redux stores?

I will preface this question by mentioning that I am new to both React and Redux...
I am working with example #14 from the BotFramework-WebChat samples.
https://github.com/microsoft/BotFramework-WebChat/tree/master/samples/14.customization-piping-to-redux
In addition, to piping the Redux action activities to change the UI, I would also like to include middleware that listens for
'DIRECT_LINE/CONNECT_FULFILLED' and then dispatches 'WEB_CHAT/SEND_EVENT' with the 'webchat/join' payload so that I can display a welcome message.
I tried just modifying dispatchIncomingActivityMiddleware.js to look like the following:
export default function (dispatch) {
return () => next => action => {
if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') {
const { activity } = action.payload;
if (
activity.type === 'event'
&& activity.from.role === 'bot'
&& activity.name === 'redux action'
) {
dispatch(activity.value);
}
} else if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'webchat/join',
value: {
language: window.navigator.language
}
}
});
}
return next(action);
};
}
Needless to say, it doesn't work. I think this is because now all of the actions are being dispatched to the second app Redux store and not the Web Chat Redux store. My question is, how do I make it do both? Is there a way to dispatch certain actions to the app Redux store and other actions to the Web Chat store? Is there another way to achieve this?
An object with two properties - dispatch and getState - gets passed to the store middleware. You should either access the dispatch property from the incoming object or deconstruct it in the function header. Try:
export default function ({ dispatch }) {
return ....
}
Also, the welcome message behavior in Web Chat has slightly changed. Take a look at this for more details.
#tdurnford thanks for your response. I was able to finally get this to work by adding the following to WebChat.js:
constructor(props) {
super(props);
this.store = createStore(
{},
({ dispatch }) => next => action => {
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'webchat/join',
value: {
language: window.navigator.language
}
}
});
}
return next(action);
},
dispatchIncomingActivityMiddleware(props.appDispatch)
);
this.state = {};
}
Now I am able to have actions dispatched to both the Web Chat redux store and the second custom redux store. Yay! The question I still have is, can you explain the difference between {dispatch} and props.appDispatch in this example? The latter seems to be passed through from the original declaration in index.js:
ReactDOM.render(
<Provider store={ store }>
<App />
</Provider>,
document.getElementById('root')
);
but what is {dispatch} referring to exactly in WebChat.js?

React-Redux unable to fetch data from firebase react native

I have created a database in firebase the schema is below:
Now All I have been trying to do is just have it show up when I do a console log but nothing shows up.
Below is the code for my JobsActions.js
import firebase from 'firebase';
import {
JOBS_FETCH_SUCCESS
} from './types';
export const jobsFetch = () => {
return (dispatch) => {
firebase.database().ref('/jobs')
.on('value', snapshot => {
dispatch({ type: JOBS_FETCH_SUCCESS, payload: snapshot.val() });
});
};
};
This is my reducer:
import {
JOBS_FETCH_SUCCESS
} from '../actions/types';
const INITIAL_STATE = {
// jobs: 'RCCA'
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case JOBS_FETCH_SUCCESS:
console.log(action);
return state;
//return action.payload;
default:
return state;
}
};
This is the JobsList
import _ from 'lodash';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { View, Text } from 'react-native';
import { jobsFetch } from '../actions';
class JobsList extends Component {
componentWillMount() {
this.props.jobsFetch();
}
render() {
return (
<View style={{ paddingTop: 20 }}>
<Text>Hello</Text>
</View>
);
}
}
export default connect(null, { jobsFetch })(JobsList);
I have authentication using firebase and its not a connection to firebase thats an issue, From what I see, it seems like maybe the ref path is wrong in the Actions file?
You main problem here is not with Firebase actually, since I believe everything else is allright, but with React-Redux.
When you are connecting a component to the store, the connect function recieves two functions. The first one (usually called mapStateToProps) recieves the state and returns an object that will be added to the props. In this case, you are not using it, so passing null is a valid decision.
The second one (usually called mapDispatchToProps) receives the dispatch as a parameter and should return an object with the functions that will be inserted to the props that can be used to dispatch new actions. In this case, you are just passing an object as the second parameter of the connect { jobsFetch }.
When you do this.props.jobsFetch(); you are actually returning the function that receives the dispatch, so nothing is actually executed.
Your mapDispatchToProps should be something similar to this
const mapDispatchToProps = dispatch => {
return {
jobsFetch : () => dispatch(jobsFetch())
}
}
export default connect(
null,
mapDispatchToProps
)(JobsList)
Here, I'm assuming that you are in fact using Redux thunk since you are returning a function that receives the dispatch as a parameter in your actions.
As you may see, we first call the jobsFetch() in order to get the function that receives the reducer, and then we dispatch it.
Let me know if this does not work! There may be something else that is not correct, but this is something that should be addressed. Hope it helps!

Apollo Client and Redux setup causes infinite render loop

I'm trying to link up React Apollo with Redux so Apollo performs the queries and mutations, and the returned data is dispatched to the Redux store in order to distribute the data around the app.
I believe I'm close to getting it right, but for some reason the app goes into an infinite loop of Redux dispatches, and I can't figure out why.
See code below:
class Admin extends Component {
constructor(props) {
super(props);
}
render({
adminAllTokens
}, {}) {
return ( /* JSX */ )
);
}
}
const AllRefreshTokens = gql `
query {
allUsers {
refreshToken
email
}
}
`;
const gqlWrapper = graphql(AllRefreshTokens, {
props: ({
ownProps,
data
}) => {
ownProps.receivedAdminTokens(data.allUsers); //dispatch to Redux store
return {
...data,
gqladminAllTokens
};
}
});
function mapStateToProps(state, ownProps) {
return {
adminAllTokens: state.auth.adminAllTokens
};
}
function mapDispatchToProps(dispatch) {
return {
receivedAdminTokens: tokens => {
dispatch(adminTokensReceived(tokens));
}
};
}
const reduxWrapper = connect(mapStateToProps, mapDispatchToProps);
export default compose(reduxWrapper, gqlWrapper)(Admin);
The adminTokensReceived() action is in the reducer file:
export const adminTokensReceived = tokens => ({
type: 'ADMIN_TOKENS_RECEIVED',
tokens
});
The GraphQL query only sends one network request, but the console is showing the ADMIN_TOKENS_RECEIVED action dispatching constantly and crashes the browser.
Thanks in advance
Whenever the Apollo HOC receives new props, it causes your action to fire, which updates the store and sends new props to your Apollo HOC, which causes your action to fire...
There's a couple of different ways you could handle this. In my mind, the most straightforward would be to drop the graphql HOC and use withApollo instead. Something like:
compose(
withApollo,
connect(mapStateToProps, mapDispatchToProps)
lifecycle({
componentDidMount() {
const { client } = this.props
client.query({ query: AllRefreshTokens })
.then(({data}) => {
receivedAdminTokens(data.allUsers)
})
.catch( //any error handling logic )
}
})
)
The above uses recompose's lifecycle but you could just as easily stick the componentDidMount method inside your component.
That said, it seems a little redundant to use Redux to store the results of your GraphQL queries when Apollo already does it for you.
Apollo's default behavior is to retrieve the data from the cache first, and only make a network request if the data doesn't exist (which is also why you only saw the one network call). That means any number of components inside your app could be wrapped with the same graphql HOC, and only the first component to be rendered would trigger a request to your GraphQL endpoint -- all other components would get their data from the cache.

Resources