I have been getting this error "Cannot read property 'user' of undefined" in a userSlice.js which by default is named as counterSlice.js in react-redux. I have tried exporting by changing names and function names too, and I guess I am exporting the right function.
any suggestions or fix that might get it running?
Here is my userSlice.js file,
import { createSlice } from '#reduxjs/toolkit';
export const userSlice = createSlice({
name: 'user',
initialState: {
user: null,
},
reducers: {
login: (state, action) => {
state.user = action.payload;
},
logout: (state) => {
state.user = null;
},
},
});
export const { login, logout} = userSlice.actions;
export const selectUser = state => state.user.user;
export default userSlice.reducer;
here is the store.js file,
import { configureStore } from '#reduxjs/toolkit';
import userReducer from '../features/userSlice';
export default configureStore({
reducer: {
counter: userReducer,
},
});
and here is the App.js file where I am trying to make the login user state to be logged in,
import React, {useEffect} from 'react';
import './App.css';
import Homescreen from "./screens/Homescreen";
import LoginScreen from "./screens/LoginScreen";
import {auth} from "./firebase";
import {useDispatch, useSelector} from "react-redux";
import {login, logout, selectUser} from "./features/userSlice";
import {BrowserRouter as Router, Switch,Route} from 'react-router-dom';
function App() {
const user = useSelector(selectUser);
const dispatch = useDispatch();
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((userAuth) => {
if(userAuth){
dispatch(login({
uid: userAuth.uid,
email: userAuth.email,
})
);
} else{
dispatch(logout())
}
});
return unsubscribe;
}, []);
return (
<div className="App">
<Router>
{!user ? (
<LoginScreen/>
):(
<Switch>
<Route exact path="/">
<Homescreen />
</Route>
</Switch>
)}
</Router>
</div>
);
}
export default App;
The problem is here
export const selectUser = state => state.user.user;
You created slice with name counter here
export default configureStore({
reducer: {
counter: userReducer,
},
});
Try
export const selectUser = state => state.counter.user;
Or rename counter to user
Related
I am using React Redux for state management. I have an anonymous user and an admin. I have current user which is empty {} and I am changing it to store the user details when the admin logs in. So, in my application I want to show the admin navbar if there is a logged user and I would like to show the anonymous navbar when there is not. Currently, when the admin logs in, the navbar is changed, but when I click logout, navbar is not changed. How can I solve this issue?
This is my NavBar.js:
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import NavBarAdmin from "./NavBarAdmin";
import NavBarAnonymous from "./NavBarAnonymous";
function NavBar() {
const currentUser = useSelector((state) => state.currentUser);
const [currentView, setCurrentView] = useState('anonymous');
useEffect(() => {
console.log(currentUser);
if(currentUser==null || currentUser.length==0 || currentUser == undefined){
setCurrentView('anonymous');
}else{
setCurrentView('admin');
}
}, [currentUser])
const handleView = () => {
switch(currentView){
case "anonymous": return <>
<NavBarAnonymous/>
</>
case "admin":
return <>
<NavBarAdmin/>
</>
}
}
return(
<>
{handleView()}
</>
)
}
export default NavBar;
My actions.js looks like:
import actionTypes from "./actionTypes";
export function setCurrentUser(value) {
return{
type: actionTypes.SET_CURRENT_USER,
payload: value
}
}
actionTypes.js
const actionTypes = {
SET_CURRENT_USER: 'SET_CURRENT_USER',
}
export default actionTypes;
configureStore.js
import { applyMiddleware, compose, createStore } from "redux";
import thunkMiddleware from "redux-thunk";
import rootReducer from './reducers';
export default function configureStore(preloadedState) {
const middlewares = [thunkMiddleware];
const middlewareEnhancer = applyMiddleware(...middlewares);
const enhancers = [middlewareEnhancer];
const composedEnhancers = compose(...enhancers);
const store = createStore(rootReducer, preloadedState, composedEnhancers);
return store;
}
reducers.js:
import { combineReducers } from "#reduxjs/toolkit";
import actionTypes from "./actionTypes";
export function currentUser(state = {}, action) {
switch (action.type) {
case actionTypes.SET_CURRENT_USER:
return Object.assign({}, state, {
q: action.payload,
})
default:
return state
}
}
export default combineReducers({
currentUser
});
In the logout functionality I am using:
dispatch(setCurrentUser({}));
And when I click logout button, {} is printed in the console, which means that the current user is set to {}, but the NavBar is not re-rendered.
I'm following a tutorial on learning Redux and I'm stuck at this point where state that should have an image url is returned as undefined.
Image is successfully saved in firbase storage and dispatched but when I try to get the url on new route with useSelector it is undefined.
import React, {useEffect} from "react";
import {useSelector} from "react-redux";
import {useHistory} from "react-router-dom";
import "./ChatView.css";
import {selectSelectedImage} from "./features/appSlice";
function ChatView() {
const selectedImage = useSelector(selectSelectedImage);
const history = useHistory();
useEffect(() => {
if(!selectedImage) {
exit();
}
}, [selectedImage])
const exit = () => {
history.replace('/chats');
}
console.log(selectedImage)
return (
<div className="chatView">
<img src={selectedImage} onClick={exit} alt="" />
</div>
)
}
export default ChatView
reducer created for chat (slice):
import { createSlice } from '#reduxjs/toolkit';
export const appSlice = createSlice({
name: 'app',
initialState: {
user:null,
selectedImage:null,
},
reducers: {
login: (state, action) => {
state.user = action.payload;
},
logout: (state) => {
state.user = null;
},
selectImage:(state, action) => {
state.selectedImage = action.payload
},
resetImage:(state) => {
state.selectedImage = null
}
},
});
export const { login, logout, selectImage, resetImage} = appSlice.actions;
export const selectUser = (state) => state.app.user;
export const selectSelectedImage = (state) => state.app.selectImage;
export default appSlice.reducer;
and code for dispatching that imageURL which when i console.log it gives the correct url:
import {Avatar} from "#material-ui/core";
import StopRoundedIcon from "#material-ui/icons/StopRounded"
import "./Chat.css";
import ReactTimeago from "react-timeago";
import {selectImage} from "./features/appSlice";
import {useDispatch} from "react-redux";
import {db} from "./firebase";
import {useHistory} from "react-router-dom";
function Chat({id, username, timestamp, read, imageUrl, profilePic}) {
const dispatch = useDispatch();
const history = useHistory();
const open = () => {
if(!read) {
dispatch(selectImage(imageUrl));
db.collection('posts').doc(id).set({read:true,}, {merge:true});
history.push('/chats/view');
}
};
return (
<div onClick={open} className="chat">
<Avatar className="chat__avatar" src={profilePic} />
<div className="chat__info">
<h4>{username}</h4>
<p>Tap to view - <ReactTimeago date={new Date(timestamp?.toDate()).toUTCString()} /></p>
</div>
{!read && <StopRoundedIcon className="chat__readIcon" />}
</div>
)
}
export default Chat
Your selector is trying to access the wrong field.
export const selectSelectedImage = (state) => state.app.selectImage;
Should actually be:
export const selectSelectedImage = (state) => state.app.selectedImage;
as your state has selectedImage field and not selectImage.
I am creating a react app, when getting data from redux. I am facing the below error message in browser. please check and let me know what am I missing.
I am using create-react-app redux-toolkit setup template to create the app
Here is my app.js:
import React from "react";
import { useSelector } from "react-redux";
import "./App.css";
import { selectUser } from "./features/userSlice";
import Header from "./components/Header";
import Sidebar from "./components/Sidebar";
import Feed from "./components/Feed";
import Login from "./components/Login";
function App() {
const user = useSelector(selectUser);
return (
<div className="App">
<Header />
{ !user ? (
<Login />
) : (
<div className="main_content">
<Sidebar />
<Feed />
</div>
)}
</div>
);
}
export default App;
below you can find the redux reducer and actions
import { createSlice } from '#reduxjs/toolkit';
export const userSlice = createSlice({
name: 'user',
initialState: {
user: null,
},
reducers: {
login: (state, action) => {
state.value = action.payload
},
logout: (state, action) => {
state.user = null
}
},
});
export const { login, logout } = userSlice.actions;
export const selectUser = (state) => state.user.user;
export default userSlice.reducer;
below is the screenshot of error which. I'am getting when running the app
Working example for you, be sure you configured your store correctly. You should separate this into responding files.
import React from "react";
import { combineReducers, createStore, createSlice } from "#reduxjs/toolkit";
import { connect, Provider, useDispatch, useSelector } from "react-redux";
// your part
const userSlice = createSlice({
name: "user",
initialState: {
user: null
},
reducers: {
login: (state, action) => {
state.user = action.payload;
},
logout: (state, action) => {
state.user = null;
}
}
});
const { login, logout } = userSlice.actions
const selectUser = (state) => state.user.user;
// what I added
const reducer = combineReducers({
user: userSlice.reducer
});
const store = createStore(reducer);
const Main = (props) => {
const dispatch = useDispatch() // I used this to check if reducers work
const user = useSelector( selectUser )
return (
<div onClick={ () => { dispatch(login({name: "Adam"})) }}>
{ !user ? "LOGIN" : "DASHBOARD "}
</div>
)
}
const mapStateToProps = (state) => ({
user: state.user
});
const Container = connect(mapStateToProps, { login, logout })(Main);
function App() {
return (
<Provider store={store}>
<Container/>
</Provider>
);
}
export default App;
I am new to working with sagas, I can’t solve the problem of "Actions must be plain objects. Use custom middleware for async actions."
I enclose all the necessary code. Already broke his head, solving the issue.
I hope for your help.
I looked at the documentation of the sagas, but did not find anything about this error.
I also watched the react boilerplate, where there are already sagas, but I would like to do this on CRA
action
import { AXIOS } from "../api";
import { takeLatest, put, call } from "redux-saga/effects";
export const GET_GENRES_PENDING = "GENRES::GET_GENRES_PENDING";
export const GET_GENRES_FULFILLED = "GENRES::GET_GENRES_FULFILLED";
export const GET_GENRES_REJECTED = "GENRES::GET_GENRES_REJECTED";
export const getGenresPending = () => ({
type: GET_GENRES_PENDING
});
export const getGenresFulfilled = data => ({
type: GET_GENRES_FULFILLED,
payload: data
});
export const getGenresRejected = error => ({
type: GET_GENRES_REJECTED,
payload: error
});
export function* getGenresAction() {
try {
yield put(getGenresPending());
const data = yield call(() => {
return AXIOS.get(
"/movie/list?api_key=5fcdb863130c33d2cb8f1612b76cbd30&language=ru-RU"
).then(response => {
console.log(response);
});
});
yield put(getGenresFulfilled(data));
} catch (error) {
yield put(getGenresRejected(error));
}
}
export default function* watchFetchGenres() {
yield takeLatest("FETCHED_GENRES", getGenresAction);
}
store
import { applyMiddleware, compose, createStore } from "redux";
import createSagaMiddleware from "redux-saga";
import rootReducer from "./reducers";
import watchFetchGenres from "./actions/getGenresAction";
const sagaMiddleware = createSagaMiddleware();
export function configureStore(initialState) {
const middleware = [sagaMiddleware];
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
rootReducer,
initialState,
composeEnhancers(applyMiddleware(...middleware))
);
sagaMiddleware.run(watchFetchGenres);
return store;
}
index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import App from "./containers/App";
import * as serviceWorker from "./serviceWorker";
import { configureStore } from "./core/configureStore.js";
const store = configureStore({});
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
serviceWorker.unregister();
App.js
import React from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
import MoviesContainer from "./MoviesContainer/MoviesContainer";
import FilterContainer from "./FilterContainer/FilterContainer";
import { Container, GlobalStyle } from "./style.js";
export default function App() {
return (
<Container className="app">
<GlobalStyle />
<Router>
<Route exact path="/" component={FilterContainer} />
<Route path="/movies" component={MoviesContainer} />
</Router>
</Container>
);
}
Container
import React, { useState, useEffect } from "react";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import watchFetchGenres from "../../core/actions/getGenresAction";
import Card from "../../components/Card/Card";
import Button from "../../components/Button/Button";
import TextInput from "../../components/TextInput/TextInput";
import { TitleH1, TitleH2, TitleCard } from "../../components/Title/Title";
import { Container, SecondaryContainer } from "../style.js";
class FilterContainer extends React.Component {
// const dispatch = useDispatch();
// useEffect(() => {
// getGenresAction();
// // fetch('https://api.themoviedb.org/3/genre/movie/list?api_key=5fcdb863130c33d2cb8f1612b76cbd30&language=en-US')
// });
componentDidMount() {
this.props.watchFetchGenres();
}
render() {
return (
<Container>
<TitleH1 title="Фильтры" />
<SecondaryContainer>
<TextInput placeholder="Введите название фильма" />
</SecondaryContainer>
<SecondaryContainer filters>
<Card>
<TitleCard title="Фильтр по жанру" />
</Card>
<Card>
<TitleCard title="Фильтр по рейтингу" />
</Card>
<Card>
<TitleCard title="Фильтр по году" />
</Card>
</SecondaryContainer>
<SecondaryContainer>
<Button primary value="Применить фильтры" placeholder="lala" />
</SecondaryContainer>
</Container>
);
}
}
const mapStateToProps = state => ({
genres: state.genres
});
const mapDispatchToProps = dispatch =>
bindActionCreators({ watchFetchGenres }, dispatch);
export default connect(
mapStateToProps,
mapDispatchToProps
)(FilterContainer);
bindActionCreators({ watchFetchGenres }, dispatch);
watchFetchGenres isn't an action creator, so this isn't correct. An action creator is function which returns an action. You have 3 examples of them in your code:
export const getGenresPending = () => ({
type: GET_GENRES_PENDING
});
export const getGenresFulfilled = data => ({
type: GET_GENRES_FULFILLED,
payload: data
});
export const getGenresRejected = error => ({
type: GET_GENRES_REJECTED,
payload: error
});
Those are the types of things you should be binding instead.
Your saga is listening for actions of type "FETCHED_GENRES", so the 3 existing action creators won't work for that. You may need to create another action creator, as in:
export const fetchGenres = () => ({
type: 'FETCHED_GENRES',
});
Then in your mapDispatchToProps, you'll make use of this action creator:
const mapDispatchToProps = dispatch =>
bindActionCreators({ fetchGenres }, dispatch);
And update where you call it:
componentDidMount() {
this.props.fetchGenres();
}
I am new to redux and I am trying to build a simple Hello World to try out this library. However, I am having trouble with getting the value in the Home component. The two buttons should trigger two different changes. I think the errors must have something to do with the connect method. After hours of research, I still cannot figure out why it does not work. Thank you in advance.
Below is my code:
Home.js -> component
import React from "react";
import { connect } from "react-redux";
import * as actionCreators from "../actions/display.js";
import { bindActionCreators } from "redux";
const Home = props => {
return (
<div>
Message:
<h1>{props.message}</h1>
<button onClick={props.sayHi}>SayHI</button>
<button onClick={props.sayHello}>Say Hello</button>
</div>
);
};
function mapStateToProps(state) {
return { ...state };
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
...actionCreators
},
dispatch
);
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
App.js
import React from "react";
import { createStore, combineReducers, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import createHistory from "history/createBrowserHistory";
import { Route } from "react-router";
import {
ConnectedRouter,
routerReducer,
routerMiddleware
} from "react-router-redux";
import Home from "./components/Home";
import reducers from "./reducers/reducer"; // Or wherever you keep your reducers
// Create a history of your choosing (we're using a browser history in this case)
const history = createHistory();
// Build the middleware for intercepting and dispatching navigation actions
const middleware = routerMiddleware(history);
// Add the reducer to your store on the `router` key
// Also apply our middleware for navigating
const store = createStore(
combineReducers({
...reducers,
router: routerReducer
}),
applyMiddleware(middleware)
);
const App = () => (
<Provider store={store}>
{/* ConnectedRouter will use the store from Provider automatically */}
<ConnectedRouter history={history}>
<Route path="/" component={Home} />
</ConnectedRouter>
</Provider>
);
export default App;
reducer.js
import { SAY_HELLO, SAY_HI } from "../constants";
const initialState = {
message: "Mark"
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case SAY_HELLO:
return { ...state, message: "Hello Mark" };
case SAY_HI:
return { ...state, message: "Hi Mark" };
default:
return state;
}
};
export default reducer;
actions/display.js
import { SAY_HELLO, SAY_HI } from "../constants";
export const sayHello = () => ({
type: SAY_HELLO
});
export const sayHi = () => ({
type: SAY_HI
});
constants.js
export const SAY_HELLO = "SAY_HELLO";
export const SAY_HI = "SAY_HI";
Update:
I figured a working solution for my code but not an ideal one. I change state=>({message:state.message}) to state=>state which means now my component subscrubes to the global state. I also change{props.message} to {props.defaultmessage} in the hi tag on Home.js. Below is the updated code.
import React from "react";
import { connect } from "react-redux";
import { sayHello, sayHi } from "../actions/display.js";
const Home = props => {
return (
<div>
Message:
{console.log(props.default.message)}
<h1>{props.default.message}</h1>
<button onClick={props.sayHi}>SayHI</button>
<button onClick={props.sayHello}>Say Hello</button>
</div>
);
};
export default connect(state => state, {
sayHello,
sayHi
})(Home);
The problem is in that part of your code:
const store = createStore(
combineReducers({
...reducers,
router: routerReducer
}),
applyMiddleware(middleware)
);
reducers variable contains reducer function, but you are using it as object here.
You should assign your reducer with a specific key in the state, for example data:
const store = createStore(
combineReducers({
data: reducers,
router: routerReducer
}),
applyMiddleware(middleware)
);
Next, message value will be available at state.data path:
function mapStateToProps(state) {
return { message: state.data.message };
}
Hoop it work!
import React from "react";
import { connect } from "react-redux";
import { sayHi, sayHello } from "../actions/display.js";
const Home = props => {
return (
<div>
Message:
<h1>{props.message}</h1>
<button onClick={props.sayHi}>SayHI</button>
<button onClick={props.sayHello}>Say Hello</button>
</div>
);
};
function mapStateToProps(state) {
return { message: state.message };
}
export default connect(mapStateToProps, { sayHi, sayHello })(Home);