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();
}
Related
I'm trying to fetch data from my rails back-end API to the redux store. I can see the data when I console.log(data) inside the middleware(createAsyncThunk). However, I can't get it into the redux store.
src/redux/doctorsSlice
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit';
import API from '../api/api';
const initialState = [];
export const getDoctors = createAsyncThunk(
'doctors/getDoctors',
async (token) => {
const response = await fetch(`${API}/doctors`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
console.log('responses', response);
if (!response.ok) throw new Error(response.statusText);
const data = await response.json();
console.log(data);
return data.doctors;
},
);
export const doctorsSlice = createSlice({
name: 'doctors',
initialState,
reducers: {},
extraReducers: {
[getDoctors.fulfilled]: (state, action) => action.payload,
},
});
export default doctorsSlice.reducer;
src/redux/store
import { configureStore } from '#reduxjs/toolkit';
import doctorsReducer from './doctorsSlice';
export default configureStore({
reducer: {
doctors: doctorsReducer,
},
});
src/components/DoctorsList
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getDoctors } from '../redux/doctorsSlice';
const DoctorsList = () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(getDoctors());
}, [dispatch]);
const { doctors } = useSelector((state) => state.doctors);
return (
<div className="">
<h2>Doctors</h2>
</div>
);
};
export default DoctorsList;
src/componets/app
import DoctorsList from './DoctorsList';
function App() {
return (
<div className="App">
<div className="content">
<DoctorsList />
</div>
</div>
);
}
export default App;
src/index
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import './index.css';
import App from './components/App';
import store from './redux/store';
import { getDoctors } from './redux/doctorsSlice';
store.dispatch(getDoctors);
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root'),
);
I have followed the redux documentation. And I expect to see data in-store. But I get an empty array.
Pls, any help, support and constructive criticism on how I can solve this are welcome.
In the getDoctors function. I was suppose to return data in the last line of the function. Instead, I was initially returning data.doctors which does not hold the data.
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
I am new to Redux and was learning how to use it with React. Basically, I did everything correctlyin terms of setting up Redux with react app but when I click on button increment I expect displaying counter to increment by one. But when I do that nothing happens and, certainly, I have checked dispatch and action being sent but basically all is ok. Thus I truly need your help guys here is the code I am using:
index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import registerServiceWorker from "./registerServiceWorker";
import { createStore } from "redux";
import reducer from "./store/reducer";
import { Provider } from "react-redux";
const store = createStore(reducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
registerServiceWorker();
reducer.js
const initialState = {
counter: 0
};
const reducer = (state = initialState, action) => {
if (action.type === "INCREMENT") {
return { counter: state.counter + 1 };
}
return state;
};
export default reducer;
counter.js
import React, { Component } from "react";
import { connect } from "react-redux";
import CounterControl from "../../components/CounterControl/CounterControl";
import CounterOutput from "../../components/CounterOutput/CounterOutput";
class Counter extends Component {
render() {
return (
<div>
<CounterOutput value={this.props.ctr} />
<CounterControl
label="Increment"
clicked={() => this.props.onIncrement}
/>
</div>
);
}
}
const mapStateToProps = state => {
return { ctr: state.counter };
};
const mapDispatchToProps = dispatch => {
return {
onIncrement: () => dispatch({ type: "INCREMENT" })
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Counter);
Did you register your reducer with store?
If not, please do that.
I believe this thing should not return string:
const mapStateToProps = state => {
return { ctr: state.counter };
};
Hello i am just starting to learn redux and am currently having a problem, i have an api i want to get information from and use it in different components i would appreciate if you help me
import React from 'react';
import { render } from 'react-dom';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import thunk from "redux-thunk";
import { createLogger } from "redux-logger";
import { BrowserRouter} from "react-router-dom";
import Reducer from './Reducers';
import App from './App';
import fetchSimcards from './Actions/fetchSimcards';
const middleware = [ thunk ];
middleware.push( createLogger() );
const store = createStore(
Reducer
applyMiddleware(...middleware),
);
import * as serviceWorker from './serviceWorker';
store.dispatch(fetchSimcards());
render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById('root')
);
serviceWorker.unregister();
and this is my action file
import * as type from '../Constans/ActionTypes';
export const ReceiveSimcards = Simcards => ({
type: type.RECEIVE_SIMCARDS,
Simcards
});
this is my reducer file
import { combineReducers } from "redux";
const Simcards = ( state = {}, action ) => {
console.log( state, action );
return state;
};
export default combineReducers({
Simcards
});
this is my container file for simcards
import React, {Component} from 'react';
import SimcardList from "../Component/SimcardList";
import { connect } from "react-redux";
class SimcardContainer extends Component {
render() {
const Simcards = this.props;
return (
<div>
<SimcardList title={"Simcards"} />
<div className="TableNumberItem">{Simcards.SimCardNumber}</div>
<div className="TableNumberItem">{Simcards.SimCardDescription}</div>
<div className="TableNumberItem">{Simcards.TeammatePrice}</div>
</div>
);
}
}
export default connect()(SimcardContainer);
and i want show this container in home page
With redux, you should call all API and handling logic code in action.
Example with action fetchAPI:
export const fetchAPI = () = async dispatch => {
let response = null;
try {
response = await axios.get('api/...')
// Example use axios
dispatch(fetchSuccess(response.data))
// To handle in reducer with redux
} catch (error) {
... Handle error here
}
}
const fetchSuccess = data => ({
type: FETCH_SUCCESS,
data: response.data
})
And in your component, you can use connect to get state and action:
import { bindActionCreators } from 'redux';
import React, { Component } from 'react';
import SimcardList from "../Component/SimcardList";
import { connect } from "react-redux";
import * as _Actions from '../../action/index'
class SimcardContainer extends Component {
componentDidMount(){
const { fetchAPI } = this.props.actions;
**fetchAPI();** // Call API here
}
render() {
const { stateReducer} = this.props;
console.log(stateReducer)
// Here, you will see data that you handled in reducer
// with action type FETCH_SUCCESS
// You should remember data that you fetch from API is asynchronous,
// So you should check like that `data && {do any thing heree}`
return (
<div>
<SimcardList title={"Simcards"} />
<div className="TableNumberItem">{Simcards.SimCardNumber}</div>
<div className="TableNumberItem">{Simcards.SimCardDescription}</div>
<div className="TableNumberItem">{Simcards.TeammatePrice}</div>
</div>
);
}
}
const mapStateToProps = state => ({
stateReducer: state
})
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(_Actions, dispatch)
})
export default connect(mapStateToProps, mapDispatchToProps)(SimcardContainer)
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);