I am working in a React Redux project.
My Reducer is like below
statusReducer.js
import { FETCH_STATUS } from "../firebase/types";
export default (state = false, action) => {
switch (action.type) {
case FETCH_STATUS:
return action.payload || null;
default:
return state;
}
};
My dispatcher is like below
Status.js
import firebase from 'firebase/app';
import { todosRef, authRef, provider } from './firebase';
import { FETCH_STATUS, FETCH_ONESTATUS } from './types.js';
const databaseRef = firebase.firestore();
export const fetchStatus = uid => async dispatch => {
var data = [];
databaseRef.collection('status').doc(uid).collection('status').get().then((snapshot) => {
snapshot.forEach((doc) => {
var snap = doc.data();
snap.key = doc.id;
data.push(snap);
console.log(data); // I am getting values here
});
dispatch({
type: FETCH_STATUS,
payload: data,
});
});
};
My Component is like below
import React from "react";
import * as actions from './firebase/Status';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom'
class Status extends React.Component {
componentWillMount() {
const { auth } = this.props;
this.props.fetchStatus(auth.uid);
};
renderStatus() {
const { status } = this.props;
const data = status ? Object.values(status) : [];
console.log(status) // I am not getting values here
}
render() {
return (
//more HTML code
);
}
}
const mapStateToProps = ({ auth, status }) => {
return {
auth,
status,
}
}
export default connect(
mapStateToProps,
actions
)(Status);
There is some problem with your Status.js file. You're fetching data from firebase and try to dispatch it. But the problem is your program dispatch before getting data form firebase.
So you can use JS Promise for that. Maybe this will help!!!
...
const request = snapshot.forEach((doc) => {
return new Promise((resolve) => {
var snap = doc.data();
snap.key = doc.id;
console.log(data); // I am getting values here
});
});
Promise.all(request).then(() => {
dispatch({
type: FETCH_STATUS,
payload: data,
});
});
...
Related
Next.js v12.0
next-redux-wrapper.
Whenever I navigate away from the page using the appropriate next/link element and then back again (using another link el) the state is reset to the initial value and so another fetch is executed. What is strange about this is that I have another 'transaction' slice setup in an identical manner except it holds an array of transaction objects and that one is working just fine (navigate away and back and the data is not re-fetched as it persisted in store) code is below any suggestions would be greatly appreciated.
store.js
import { HYDRATE, createWrapper } from "next-redux-wrapper";
import thunkMiddleware from "redux-thunk";
import address from "./address/reducer";
import transactions from "./transaction/reducer";
const bindMiddleware = (middleware) => {
if (process.env.NODE_ENV !== "production") {
const { composeWithDevTools } = require("redux-devtools-extension");
return composeWithDevTools(applyMiddleware(...middleware));
}
return applyMiddleware(...middleware);
};
const combinedReducer = combineReducers({
transactions,
address,
});
const rootReducer = (state, action) => {
if (action.type === HYDRATE) {
const nextState = {
...state, // use previous state
...action.payload, // apply delta from hydration
};
if (state.address.id){
nextState.address = state.address;
}
return nextState;
} else {
return combinedReducer(state, action);
}
};
const initStore = () => {
return createStore(rootReducer, bindMiddleware([thunkMiddleware]));
};
export const wrapper = createWrapper(initStore);
address/reducer.js
const addressInitialState = {
id: null,
timestamp: null,
address: null,
balance: null,
received: null,
sent: null,
groupid: null,
last_txs: []
};
export default function reducer(state = addressInitialState, action) {
switch (action.type) {
case addressActionTypes.GET_WALLET_DETAILS:
return {id: action.payload.address, ...action.payload};
default:
return state;
}
}
address/action.js
export const addressActionTypes = {
GET_WALLET_DETAILS: "GET_WALLET_DETAILS",
};
export const getWalletDetails = (address) => {
return async (dispatch) => {
const fetchData = async () => {
const response = await fetch(
`https:someapi.com/api/getaddress/?address=${address}`
);
if (!response.ok) {
throw new Error("Could not fetch address data!");
}
const data = await response.json();
console.log('req sent');
return data;
};
try {
const addressData = await fetchData();
dispatch({
type: addressActionTypes.GET_WALLET_DETAILS,
payload: addressData,
});
} catch (err) {
console.log(err);
}
};
};
pages/[address].js
import { Fragment } from "react";
import Head from "next/head";
import AddressDetails from "../../../components/crypto/rvn/AddressDetails";
import AddressTransactions from "../../../components/crypto/rvn/AddressTransactions";
import { connect } from "react-redux";
import { getWalletDetails } from "../../../store/address/action";
import { wrapper } from "../../../store/store";
function Address(props) {
return (
<Fragment>
<Head>
<title>RVN</title>
<meta name="description" content="RVN Address" />
</Head>
<AddressDetails address={props.addressDetails}></AddressDetails>
<AddressTransactions
transactions={props.addressDetails["last_txs"]}
address={props.addressDetails.address}
></AddressTransactions>
</Fragment>
);
}
export const getServerSideProps = wrapper.getServerSideProps(
(store) => async (context) => {
const state = store.getState();
if(state.address.id === null) {
await store.dispatch(getWalletDetails(context.params.address));
}
else{
return{
props: {
addressDetails: state.address,
}
}
}
}
);
const mapStateToProps = (state) => ({
addressDetails: state.address,
});
export default connect(mapStateToProps, null)(Address);
Solved this by converting this
const initStore = () => {
return createStore(rootReducer, bindMiddleware([thunkMiddleware]));
};
in store.js
which direclty from https://github.com/vercel/next.js/tree/canary/examples/with-redux-thunk
to this
const store = createStore(rootReducer, bindMiddleware([thunkMiddleware]));
const initStore = () => {
return store
};
so it does not reinitialize the store every time the wrapper is used
this is more in line with the documentation at
https://github.com/kirill-konshin/next-redux-wrapper
I'm trying to access data from my API using Redux but when redux tool kit is showing me its an empty array. The api I've populated using postman and the post method seem to work perfectly fine, but attempting to use the get method to access that data it shows an empty array. My DB has the data though. My Data is an array of Object i.e. [ {...} , {...} , {...} ]
API
import axios from "axios";
const url = "http://localhost:5000/info"
export const fetchInfo = () => axios.get(url);
export const createInfo = (newInfo) => axios.post(url, newInfo);
ACTIONS
import * as api from "../api/index.js";
//constants
import { FETCH_ALL, CREATE } from "../constants/actiontypes";
export const getInfo = () => async (dispatch) => {
try {
const { data } = await api.fetchInfo();
console.log(data);
dispatch({ type: FETCH_ALL, payload: data });
} catch (error) {
console.log(error);
}
};
export const createInfo = (info) => async (dispatch) => {
try {
const { data } = await api.createInfo(info);
dispatch({ type: CREATE, payload: data });
} catch (error) {
console.log(error);
}
};
REDUCER
import { FETCH_ALL, CREATE } from "../constants/actiontypes";
export default (infos = [], action) => {
switch (action.type) {
case FETCH_ALL:
return action.payload;
case CREATE:
return [...infos, action.payload];
default:
return infos;
}
};
COMBINE REDUCERS
import {combineReducers} from "redux";
import infos from "./info"
export default combineReducers({infos})
Component I'm trying to to display it in
import React from "react";
//redux
import { useSelector } from "react-redux";
//component
import MovieDetail from "./MovieDetail"
const MovieTitles = () => {
const infos = useSelector((state) => state.infos);
console.log(infos) // shows me empty array
return (
<div>
{infos.map((i) => (
<MovieDetail info={i} />
))}
</div>
);
};
export default MovieTitles;
Is there something else I'm missing which allows to me to access the data?
thanks
I'm actually working on a small react app, i have an action to check if the current user exist in on the firestore collection 'users' based on the uid, anad then get the user’s profile information.
It works actually this action, but i can't use it in my profile component to display it !
That's the action file:
import 'firebase/firestore'
import firebase from 'firebase/app'
const getUser =()=>{
return (dispatch)=>{
firebase.auth().onAuthStateChanged(firebaseUser => {
if(firebaseUser){
firebase.firestore().collection("users").doc(firebaseUser.uid).get().then( doc => {
const { displayName } = doc.data()
//it works and it shows me on console the name i want
console.log("display name in action: ",displayName)
const currentUser = {
uid: firebaseUser.uid,
displayName
}
dispatch({
type:'GET_USER',
currentUser,
})
})
}
})
}
}
export default getUser ;
when i try to console log it in my profile file, it shows this error "typeError: undefined is not an object (evaluating 'this.props.getUser().currentUser')":
console.log("getting current user: ", this.props.getUser().currentUser )
I expect to display me the displayName but i got that error!
You actually looking for reducer. Action handler is not designed to return data to your component. Action idea is to store data to reducer.
Code below assumes that you have properly connected react-redux with your application.
src/actions/userAction.js
import 'firebase/firestore'
import firebase from 'firebase/app'
export const getUser = () => {
return (dispatch) => {
firebase.auth().onAuthStateChanged(firebaseUser => {
if (firebaseUser) {
firebase.firestore().collection("users").doc(firebaseUser.uid).get().then(doc => {
const {displayName} = doc.data();
const currentUser = {
uid: firebaseUser.uid,
displayName
};
dispatch({
type: 'GET_USER',
payload: currentUser
});
})
}
})
}
};
src/reducers/userReducer.js
const INITIAL_STATE = {
data: {},
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'GET_USER':
return {
...state,
data: action.payload
};
default:
return state;
}
};
src/reducers/index.js
import userReducer from "./userReducer";
export default {
user: userReducer
};
src/components/Example.js
import React from 'react';
import connect from "react-redux/es/connect/connect";
import {getUser} from "../actions/userAction";
class Example extends React.Component {
componentDidMount() {
this.props.getUser();
}
render() {
if (!Object.keys(this.props.user.data).length)
return <div>Loading user's data</div>;
return (
<div>
{ JSON.stringify(this.props.user.data) }
</div>
);
}
}
const mapStateToProps = (state) => {
return {
user: state.user
};
};
export default connect(mapStateToProps, {
getUser,
})(Example);
I am in learning phase of react, and creating small application which fetches user wishlist from firebase table and updated redux store and I am trying to access that redux store in render method but when i console.log this.props.wishlist in render method its shows null. Redux state is updated correctly. Checked with redx dev tool.
redux state screenshot
Action creator which gets wishlist data from firebase API
export const fetchWishlist = (email)=> {
return dispatch => {
dispatch(fetchWishlistStart());
let rawMovieId=[];
let uniqueMovieIdList = [];
const queryParams ='?orderBy="email"&equalTo="'+email+'"';
axios.get('https://movie-project-6fc34.firebaseio.com/wishlist.json'+queryParams)
.then (response=>{
for(let key in response.data){
rawMovieId.push(response.data[key].movieId)
}
uniqueMovieIdList = [ ...new Set(rawMovieId) ];
dispatch(fetchMovieDetailsForWishlist(uniqueMovieIdList))
})
.catch(error=> {
console.log(error);
})
}
}
export const setMovieDetailsForWishlist = (movieDetailsList)=> {
return {
type:actionType.SET_MOVIEDETAILS_WISHLIST,
movieDetailsList:movieDetailsList
}
}
export const fetchMovieDetailsForWishlist = (movieList) => {
return dispatch => {
dispatch(fetchWishlistSuccess());
let updatedMovieList = []
movieList.map((currItem)=>{
let final_api_url = api_url+movieDetails_api_end_point+currItem+api_key+'&language='+language
axios.get(final_api_url)
.then(response=>{
updatedMovieList.push({
title:response.data.title,
movieId:response.data.id,
poster:response.data.poster_path
})
})
.catch(error=>{
console.log(JSON.stringify(error));
})
})
dispatch(setMovieDetailsForWishlist(updatedMovieList));
}
}
WhislistReducer --
import * as actionType from '../actions/actionType.js'
const intialState = {
wishList:null,
showLoader:false
}
const wishListReducer = (state=intialState, action) => {
switch (action.type) {
case actionType.FETCH_WISHLIST_START:
return {
...state,
showLoader:true
}
case actionType.FETCH_WISHLIST_SUCCESS:
return {
...state,
showLoader:false
}
case actionType.SET_MOVIEDETAILS_WISHLIST:
return {
...state,
showLoader:false,
wishList:action.movieDetailsList
}
default:
return state
}
}
export default wishListReducer;
wishlist component
import React, { Component } from 'react';
import {connect} from 'react-redux';
import {withRouter} from 'react-router-dom';
import * as action from '../store/actions/index'
export class Wishlist extends Component {
componentDidMount() {
this.props.fetchWishlist(window.localStorage.getItem('email'));
render() {
let wishListPageContent = '<div> Loading........</div>'
let userWishlistDetails = this.props.wishlist
console.log(userWishlistDetails);
if (!this.props.showLoader) {
wishListPageContent = (
<div> wishlist component</div>
)
}
return (
<div>
{wishListPageContent}
</div>
);
}
}
const mapStateToProps = state => {
return {
userEmail:state.authState.userEmail,
wishlist:state.wishlistState.wishList,
isAuthSuccess:state.authState.isAuthSuccess,
showLoader:state.wishlistState.showLoader
}
}
const mapDispatchToProps = dispatch => {
return {
fetchWishlist:(email)=>dispatch(action.fetchWishlist(email)),
fetchMovieDetailsForWishlist:(movieList)=>dispatch(action.fetchMovieDetailsForWishlist(movieList))
}
}
export default withRouter(connect(mapStateToProps,mapDispatchToProps)(Wishlist));
I recently started using redux for a new personal project. It worked pretty well until I started using "combineReducers". Whenever I click "Fetch todos" both my user as well as my todo reducer get updated and even though they have different data field names both get the same data. Now I probably did some wrong encapsulation here. But no matter how often I went over the docs, I just cannot see what I am doing wrong.
My store initialization script:
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import toDoReducer from './todos/reducer';
import userReducer from './users/reducer';
const rootReducer = combineReducers({
todosSlice: toDoReducer,
usersSlice: userReducer
});
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk)));
export default store;
gets injected into index:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './containers/app/App';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux';
import configureStore from './store/configureStore';
ReactDOM.render(<Provider store={ configureStore }><App /></Provider>, document.getElementById('root'));
serviceWorker.unregister();
My app hold the logic for the todo container
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as todoActions from '../../store/todos/actions';
import UserContainer from '../usersContainer/UserContainer';
class App extends Component {
componentDidMount() {
console.log(this.props);
}
render() {
let loading = '';
let error = '';
let todos = [];
// check whether the component is fetching data
this.props.loading === true ? loading = <p>Loading...</p> : loading = '';
// check if there was an error
this.props.error && this.props.loading === false ? error = <p>There was an error</p> : error = '';
// map the todos in the desired html markup.
todos = this.props.todos.map( todo => {
return <div key={todo.id}> name: {todo.title} </div>
});
return (
<div className="App">
{/* <UserContainer /> */}
{loading}
{error}
<p onClick={() => this.props.onFetchTodos()}>Fetch Todos</p>
{todos}
</div>
);
}
}
const mapStateToProps = state => {
return {
error: state.todosSlice.error,
loading: state.todosSlice.loading,
todos: state.todosSlice.todos
}
}
const mapDispatchToProps = dispatch => {
return {
onFetchTodos: () => dispatch(todoActions.fetchTodos())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
Which has the following actions:
import axios from 'axios';
export const FETCH_TODOS = 'FETCH_TODOS';
export const GET_TODOS_STARTED = 'GET_TODOS_STARTED';
export const FETCH_TODOS_SUCCESS = 'FETCH_TODOS_SUCCESS';
export const FETCH_TODOS_FAILURE = 'FETCH_TODOS_FAILURE';
export const fetchRequest = () => {
return dispatch => {
dispatch(getTodoStarted());
axios.get('https://one365-api-dev.azurewebsites.net/api/teams/')
.then(result => {
dispatch(fetchTodosSucces(result));
}).catch(error => {
dispatch(fetchTodoFailure(error));
});
}
}
const getTodoStarted = () => ({
type: GET_TODOS_STARTED
});
const fetchTodosSucces = todos => ({
type: FETCH_TODOS_SUCCESS,
payload: {
...todos
}
});
const fetchTodoFailure = error => ({
type: FETCH_TODOS_FAILURE,
payload: {
error
}
});
export const fetchTodos = () => {
return (dispatch => {
dispatch(fetchRequest());
});
}
And it's reducer
import * as actions from './actions';
const initialState = {
error: null,
loading: false,
todos: []
}
const todosReducer = (state = initialState, action) => {
switch(action.type) {
case actions.GET_TODOS_STARTED: {
console.log('fetch todo state', state)
return {
...state,
loading: state.loading = true
};
}
case actions.FETCH_TODOS_SUCCESS: {
const todos = action.payload.data;
return {
...state,
loading: false,
todos: state.todos = todos
};
}
case actions.FETCH_TODOS_FAILURE: {
const error = action.payload.error;
return {
...state,
loading: false,
error: state.error = error
};
}
default: {
return state;
}
}
}
export default todosReducer;
The Users Component
import React from 'react';
import { connect } from 'react-redux';
import * as userActions from '../../store/users/actions';
class UserContainer extends React.Component {
render () {
let loading = '';
let error = '';
let users = [];
// check whether the component is fetching data
this.props.usersLoading === true ? loading = <p>Loading...</p> : loading = '';
// check if there was an error
this.props.usersError && this.props.loading === false ? error = <p>There was an error</p> : error = '';
// map the users in the desired html markup.
users = this.props.users.map( user => {
return <div key={user.id}> name: {user.title} </div>
});
return (
<div className="Users">
{loading}
{error}
<p onClick={() => this.props.onFetchUsers()}>Fetch Users</p>
{users}
</div>
);
}
}
const mapStateToProps = state => {
return {
usersError: state.usersSlice.error,
usersLoading: state.usersSlice.loading,
users: state.usersSlice.users
}
}
const mapDispatchToProps= (dispatch) => {
return {
onFetchUsers: () => dispatch(userActions.fetchUsers())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(UserContainer);
the user actions:
import axios from 'axios';
export const FETCH_USERS = 'FETCH_TODOS';
export const FETCH_USERS_STARTED = 'GET_TODOS_STARTED';
export const FETCH_USERS_SUCCESS = 'FETCH_TODOS_SUCCESS';
export const FETCH_USERS_FAILURE = 'FETCH_TODOS_FAILURE';
export const fetchRequest = () => {
return dispatch => {
dispatch(fetchUsersStarted());
axios.get('https://one365-api-dev.azurewebsites.net/api/me')
.then(result => {
dispatch(fetchUsersSuccess(result));
}).catch(error => {
dispatch(fetchUsersFailure(error));
});
}
}
export const fetchUsersSuccess = (users) => {
return {
type: FETCH_USERS_SUCCESS,
payload: {
...users
}
}
}
export const fetchUsersStarted = () => ({
type: FETCH_USERS_STARTED
});
export const fetchUsersFailure = (error) => {
return {
type: FETCH_USERS_FAILURE,
payload: {
error
}
}
}
export const fetchUsers = () => {
return dispatch => {
dispatch(fetchRequest())
}
};
And it's reducer:
import * as actions from './actions';
const initialState = {
error: '',
loading: false,
users: []
}
const userReducer = (state = initialState, action) => {
switch(action.type) {
case actions.FETCH_USERS_STARTED: {
console.log('fetch users state', state)
return {
...state,
loading: state.loading = true
}
}
case actions.FETCH_USERS_SUCCESS: {
const users = action.payload.data;
return {
...state,
loading: false,
users: state.users = users
}
}
case actions.FETCH_USERS_FAILURE: {
const error = state.payload.error;
return {
...state,
loading: false,
error: state.error = error
}
}
default: {
return state;
}
}
}
export default userReducer;
Now when I run my DEV server I only see the fetch todo button. I commented out the users on click handler to see if it was an event bubble going up. Bu t this wasn't the case.
Once the app load redux dev tools shows the state as follows:
but once i click the fetch todo's handler. Both todos and users get filled.
I appreciate anyone who read though so much (boilerplate) code. I probably made a problem encapsulating my state. but again after reading many tutorials I still cannot find my issue.
You have a copy/paste issue. You changed the names of the constants for your "USERS" actions, but left the values the same as the "TODOS" actions.
export const FETCH_USERS = 'FETCH_TODOS';
export const FETCH_USERS_STARTED = 'GET_TODOS_STARTED';
export const FETCH_USERS_SUCCESS = 'FETCH_TODOS_SUCCESS';
export const FETCH_USERS_FAILURE = 'FETCH_TODOS_FAILURE';
I assume you meant to have:
export const FETCH_USERS = 'FETCH_USERS';
export const FETCH_USERS_STARTED = 'FETCH_USERS_STARTED';
export const FETCH_USERS_SUCCESS = 'FETCH_USERS_SUCCESS';
export const FETCH_USERS_FAILURE = 'FETCH_USERS_FAILURE';