Although I have successfully fetched users and profiles but I don't know why this error below occurs:
Cannot read property 'content' of undefined
{content: "Hello world", status: 2}
Initially, I use Firestore and this time fetch 2 docs from different collections, users and profiles.
redux code:
export function fetchUser(id) {
return dispatch => {
usersRef
.doc(id)
.get()
.then(doc => {
profilesRef
.where("uid", "==", id)
.where("status", "==", doc.data().current_status)
.get()
.then(function(querySnapshot) {
querySnapshot.forEach(function(snapshot) {
const profileItems = { ...doc.data(), profiles: snapshot.data() };
dispatch({
type: FETCH_USER,
payload: profileItems
});
});
});
});
};
}
In container:
import { FETCH_USER } from "../actions";
const initialState = {};
export default (state = initialState, action) => {
switch (action.type) {
console.log("action", action.payload)
// successfully fetched, user info and profiles: profiles info
case FETCH_USER:
return action.payload;
default:
return state;
}
};
In view:
function mapStateToProps({ users }) {
console.log("content", user.profiles.content)
// Cannot read property 'content' of undefined
return { user: users, profiles: user.profiles };
}
reducer file:
import { createStore, applyMiddleware, combineReducers } from "redux";
import UsersReducer from "./reducer_users.js";
const rootReducer = combineReducers({
users: UsersReducer,
});
const store = createStore(rootReducer, applyMiddleware(reduxThunk));
export default store;
Here you are making mistack you are accessing user instead of users
function mapStateToProps({ users , content }) {
console.log("content", user.profiles.content) // change user to users
console.log("content",content) // you can directly access content here
return { user: users, profiles: user.profiles };
}
you can initialize default state here
add one more type FETCH_USER_COMPLETE
import { FETCH_USER } from "../actions";
const initialState = {
content : '',
users : ''
};
export default (state = initialState, action) => {
switch (action.type) {
console.log("action", action.payload)
// successfully fetched, user info and profiles: profiles info
case FETCH_USER:
return { ...state , action.payload };
case FETCH_USER_COMPLETE :
return { ...state , users : action.payload.users , content : action.payload.users.profiles.content };
default:
return state;
}
};
I think it might not be best practice but I have solved like this:
from:
querySnapshot.forEach(function(snapshot) {
const profileItems = { ...doc.data(), profiles: snapshot.data() };
dispatch({
type: FETCH_USER,
payload: profileItems
});
});
to:
querySnapshot.forEach(function(snapshot) {
const userItems = { ...doc.data(), ...snapshot.data() };
dispatch({
type: FETCH_USER,
payload: userItems
});
});
I can get content like {user.content}.
Related
I am implementing the delete product function by redux-saga and this is my deleteProduct function code
import firebaseApp from "./config";
const firebaseDb = firebaseApp.database();
export const deleteProduct = (productId) => {
return firebaseDb
.ref("products")
.child(productId)
.remove()
.then(() => {
console.log(`Deleted ${productId}`)
return { status: "ok" }
})
.catch(() => ({ status: "error" }));
}
After I run click to delete button, it already shows that the product id has been deleted, but the product data doesn't delete from the database.
This is my reducer
const deleteProductRequest = (state, action) => ({
...state,
loading: true,
type: action.type,
});
const deleteProductSuccess = (state, action) => ({
...state,
loading: false,
product: action.data,
type: action.type,
});
const deleteProductFailure = (state, action) => ({
...state,
loading: false,
error: action.error,
type: action.type,
});
And this is my saga
export function* deleteProductRequest(action) {
try {
const { productId } = action;
const response = yield call(deleteProduct, productId);
if ((response.status === "ok")) {
yield put(Creators.deleteProductSuccess(response.product));
} else {
yield put(Creators.deleteProductFailure(response.error));
}
} catch (error) {
yield put(Creators.deleteProductFailure(error));
}
}
My container is like this
import { connect } from "react-redux";
import { Creators } from "../../actions/productAction";
import ProductTable from "../../pages/ProductTable";
const mapStateToProps = (state) => ({
products: state.product.products,
product: state.product.product,
loading: state.product.loading,
error: state.product.error,
});
const mapDispatchToProps = {
...Creators,
};
const PostNewContainer = connect(
mapStateToProps,
mapDispatchToProps
)(ProductTable);
export default PostNewContainer;
First of all, I get all list of product from Firebase to a Table, and then I start to implement product action and step by step to API, reducer, and Saga
Anyone understand on this case, could you please support me. Thank you so much
firebaseDb.ref("products").child(productId) is a Reference and a Reference does not have a removeValue()method.
You should use the remove() method as follows:
return firebaseDb
.ref("products")
.child(productId)
.remove();
I am new with reac-redux, I am trying to get collection from Firestore but now when firebase returns the data and I try to map the info to storage through redux-observable I get an error "Actions must be plain objects. Use custom middleware for async actions." I guess it must be about the epic configuration, then I leave the code
Epic
import { getFirestore } from "redux-firestore";
import {
GET_DOCUMENTS,
GET_COLLECTIONS_BY_DOCUMENT,
setStatus,
getDocumentsSuccess,
getDocumentsFailed
} from "../actions/dataActions";
import { switchMap } from "rxjs/operators";
import { ofType } from "redux-observable";
import { concat, of } from "rxjs";
export default function dataEpics(action$) {
const getFS = getFirestore();
return action$.pipe(
ofType(GET_DOCUMENTS, GET_COLLECTIONS_BY_DOCUMENT),
switchMap(action => {
if (action.type === GET_DOCUMENTS) {
return concat(
of(setStatus("pending")),
getFS
.collection("en")
.get()
.then(querySnapshot => {
let listDocumentIds = [];
querySnapshot.forEach(doc => {
listDocumentIds.push(doc.id);
getDocumentsSuccess(listDocumentIds);
});
})
.catch(err => of(getDocumentsFailed(err)))
);
}
})
);
}
Action
export const SET_STATUS = "SET_STATUS";
export const GET_DOCUMENTS = "GET_DOCUMENTS";
export const GET_DOCUMENTS_SUCCESS = "GET_COLLECTIONS_SUCCESS";
export const GET_DOCUMENTS_FAILED = "GET_COLLECTIONS_FAILED";
export function setStatus(status) {
return {
type: SET_STATUS,
payload: status
};
}
export function getDocumentsSuccess(documents) {
return {
type: GET_DOCUMENTS_SUCCESS,
payload: documents
};
}
reducer
import {
GET_DOCUMENTS_SUCCESS,
GET_DOCUMENTS_FAILED,
SET_STATUS
} from "../actions/dataActions";
const initState = {
status: "idle", // "idle" | "logout" | "pending" | "login" | "success" | "failure";
documents: [],
collections: []
};
const dataReducers = (state = initState, action) => {
switch (action.type) {
case SET_STATUS: {
return {
...state,
status: action.payload
};
}
case GET_DOCUMENTS_SUCCESS: {
return {
...state,
status: "success",
documents: action.payload
};
}
default:
return state;
}
};
export default dataReducers;
I think the error is in the epic, I have more code in a similar way
Thanks for help me.
I found the solution, the error was in the epic, I was trying to call the action inside querySnapshot, this is no possible, then I move the getDocumentsSuccess after
getFS
.collection(action.payload.language + "_" + "MachinesAndEquipment")
.get()
.then(querySnapshot => {
let listDocumentIds = [];
querySnapshot.forEach(doc => {
listDocumentIds.push(doc.id);
});
getDocumentsSuccess(listDocumentIds);
I'm trying to update and delete some notes that are on cloud firestore via redux, every thing seems to work fine but i should reload the page to see the results
import { combineReducers } from 'redux';
import authReducer from './authReducer';
import noteReducer from './noteReducer';
import { firestoreReducer } from 'redux-firestore';
import { firebaseReducer } from 'react-redux-firebase';
const rootReducer = combineReducers({
auth: authReducer,
note: noteReducer,
firestore: firestoreReducer,
firebase: firebaseReducer
});
export default rootReducer;
const store = createStore(rootReducer,
composeEnhancers(
applyMiddleware(thunk.withExtraArgument({getFirebase, getFirestore})),
reduxFirestore(fbConfig),
reactReduxFirebase(fbConfig, {useFirestoreForProfile: true, userProfile: 'users', attachAuthIsReady: true})
)
);
this is my note action:
export const createNote = (note) => {
return (dispatch, getState, {getFirebase, getFirestore}) => {
const firestore = getFirestore();
const profile = getState().firebase.profile;
const authorId = getState().firebase.auth.uid;
firestore.collection('notes').add({
...note,
authorFirstName: profile.firstName,
authorLastName: profile.lastName,
authorId: authorId,
createdAt: new Date(),
})
.then(() => {
dispatch({type: 'CREATE_NOTE_SUCCESS', note});
})
.catch((err) => {
dispatch({type: 'CREATE_NOTE_ERROR', err});
})
};
};
export const updateNote = (updatedNote, id) => {
return (dispatch, getState, { getFirestore }) => {
const firestore = getFirestore();
firestore.collection('notes').doc(id.toString()).update({
...updatedNote,
createdAt: new Date(),
})
.then(() => {
dispatch({type: 'UPDATE_NOTE_SUCCESS'}, updatedNote)
})
.catch(() => {
dispatch({type: 'UPDATE_NOTE_ERROR'})
})
};
};
export const deleteNote = (id) => {
return (dispatch, getState, { getFirestore }) => {
const firestore = getFirestore();
firestore.collection('notes').doc(id.toString()).delete()
.then(() => {
dispatch({type: 'DELETE_NOTE_SUCCESS'}, id);
})
.catch(() => {
dispatch({type: 'DELETE_NOTE_ERROR'})
});
};
};
this is my notereducer
import * as actionTypes from '../actions/actionTypes';
import { updateObject } from '../../../shared/utility';
const initState = {
notes: [],
id: null,
error: null,
changed: false
};
const noteReducer = (state = initState, action) => {
switch (action.type) {
case actionTypes.CREATE_NOTE_SUCCESS:
return updateObject(state, {error: null , changed: true})
case actionTypes.CREATE_NOTE_ERROR:
return updateObject(state, {error: action.err});
case actionTypes.UPDATE_NOTE_SUCCESS:
return updateObject(state, {error: null, changed: true});
case actionTypes.UPDATE_NOTE_ERROR:
return updateObject(state, {error: action.err});
case actionTypes.DELETE_NOTE_SUCCESS:
return updateObject(state, {
notes: state.notes.filter(note => note.id !== action.id),
error: null,
changed: true
});
case actionTypes.DELETE_NOTE_ERROR:
return updateObject(state, {error: action.err});
default:
return state;
}
};
export default noteReducer;
here are some parts of my code, i don't know what part I've done wrong or what I've missed, i really appreciate your help
I am using multiple reducers in my project and then combining them with combineReducers() function and have all actions in single file. when i dispatch the action, it is returning me state values to undefined. I think It can't find out because of multiple reducerse. But when i use single reducer file. It is working fine. Can anyone please tell me what the issue.It is how i am combining the reducers.
const rootReducer = combineReducers({
isMobileReducer,
imageSliderReducer
})
and now passing to store, like below:
let store = createStore(rootReducer,applyMiddleware(thunk))
and in frontend how i am accessing state
const mapStateToProps = (state) => ({
images: state.images,
isMobile: state && state.isMobile
})
imageSliderReducer.js
import {
FETCH_IMAGES_BEGIN,
FETCH_IMAGES_SUCCESS,
FETCH_IMAGES_FAILURE
} from '../actions/actionTypes'
const initialState = {
images:[],
error:null
}
const imageSliderReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_IMAGES_BEGIN:
return {...state,error:null}
case FETCH_IMAGES_SUCCESS:
return {...state,images:action.payload.images}
case FETCH_IMAGES_FAILURE:
return {...state,error:action.payload.error,images:[]}
default:
return state
}
}
export default imageSliderReducer;
isMobileReducer.js
import {
OPEN_MENU,
CLOSE_MENU,
SET_DEVICE_TYPE,
} from '../actions/actionTypes'
const initialState = {
isMenuOpen: null,
isMobile: false
}
const isMobileReducer = (state = initialState, action) => {
switch (action.type) {
case OPEN_MENU:
return {...state, isMenuOpen: true}
case CLOSE_MENU:
return {...state, isMenuOpen: false}
case SET_DEVICE_TYPE:
return {...state, isMobile: action.isMobile}
default:
return state
}
}
export default isMobileReducer;
actionCreator.js
import {
OPEN_MENU,
CLOSE_MENU,
SET_DEVICE_TYPE,
FETCH_IMAGES_BEGIN,
FETCH_IMAGES_SUCCESS,
FETCH_IMAGES_FAILURE
} from './actionTypes'
export function openMenu(isMobile) {
return {
type: OPEN_MENU
}
}
export function closeMenu(isMobile) {
return {
type: CLOSE_MENU
}
}
export function setDeviceType (isMobile) {
return {
type: SET_DEVICE_TYPE,
isMobile: isMobile
}
}
export function fetchImages() {
return dispatch => {
dispatch(fetchImagesBegin());
return fetch("https://7344.rio.com/wp-json/customapi/homeslider")
.then(handleErrors)
.then(res => res.json())
.then(json => {
dispatch(fetchImagesSuccess(json.posts));
return json.posts;
})
.catch(error => dispatch(fetchImagesFailure(error)));
};
}
function handleErrors(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
export const fetchImagesBegin = () => ({
type: FETCH_IMAGES_BEGIN
});
export const fetchImagesSuccess = images => ({
type: FETCH_IMAGES_SUCCESS,
payload: { images }
});
export const fetchImagesFailure = error => ({
type: FETCH_IMAGES_FAILURE,
payload: { error }
});
Try using this:
const mapStateToProps = (state) => ({
images: state.imageSliderReducer.images,
isMobile: state.isMobileReducer.isMobile
})
Reducer 1 code is as below. I want to call another reducer method after successful authetication of user. so its based of response of reducer 1 , I want to call method/action of reducer 2.
const LOGIN = 'redux-example/auth/LOGIN';
const LOGIN_SUCCESS = 'redux-example/auth/LOGIN_SUCCESS';
const LOGIN_FAIL = 'redux-example/auth/LOGIN_FAIL';
import { browserHistory } from 'react-router';
import { apiurl } from '../../Constants';
import {savedata} from '../../redux/modules/new';
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case LOGIN:
return {
...state,
loggingIn: true
};
case LOGIN_SUCCESS:
return {
...state,
loggingIn: false,
user: action.result
};
case LOGIN_FAIL:
return {
...state,
loggingIn: false,
user: null,
loginError: action.error
};
default:
return state;
}
}
export function login(page,email,password) {
var querystring = require('querystring');
if(action == undefined) action = null;
var data = querystring.stringify({
email: email,
password: password
});
return {
types: [LOGIN, LOGIN_SUCCESS, LOGIN_FAIL],
promise: (client) => client.post(apiurl + 'ajax/login', {
data: data
}).then(response => {
//console.log(response);
switch(page){
case 'signin':
if(response.auth == 'true') {
redirectuser(response);
}
break;
default:
break;
}
return response;
})
.catch( error => Promise.reject(error))
};
}
export function redirectuser(response) {
console.log('response is as below');
console.log(response);
if(response.action == 'action1'){
savedata();
// here I want call another reducer method save data
}
}
When I call action save data of reducer 2 from reducer 1 , it does not work. How to dispatch action of reducer 2 from reducer 1.
Edit 1: my middleware code is as below
export default function clientMiddleware(client) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
const { promise, types, ...rest } = action; // eslint-disable-line no-redeclare
if (!promise) {
return next(action);
}
const [REQUEST, SUCCESS, FAILURE] = types;
next({ ...rest, type: REQUEST });
const actionPromise = promise(client, dispatch);
actionPromise.then(
result => next({ ...rest, result, type: SUCCESS }),
error => next({ ...rest, error, type: FAILURE })
).catch(error => {
next({ ...rest, error, type: FAILURE });
});
return actionPromise;
};
}
Dispatching an action inside a reducer is not a good move. As i understand, you have to do some update synchronously. One way is, once the first reducer is updated, where ever your are consuming that reducer go and inside componentWillReceiveProps or componentDidUpdate do something like.
NOTE: before dispatching you have to import the configurestore and create a const dispatch from store.
componentWillReceiveProps(nextProps)
{
//only if user was not there previously and now user is there
if(!this.props.user && nextProps.user)
{
dispatch({type: SECOND_ACTION, payLoad})
}
}