I want to pass the state from my searchReducer to my movieReducer. The search takes in an input and saves the id of the movie(s), into state, I want to take that id value and pass it into the fetch for my movies, so that I can fetch each movie with the id and save the data into the movieReducer's state. How can I do this?
actions.js
// ------------------ SEARCH ------------------
export const searchMovie = text => dispatch => {
dispatch({
type: SEARCH_MOVIE,
payload: text
})
}
export const fetchSearch = text => dispatch => {
axios.get(`https://api.themoviedb.org/3/search/multi?api_key=API_KEY&language=en-US&query=${text}&page=1&include_adult=false`)
.then(response => dispatch({
type: FETCH_SEARCH,
payload: response.data.results.map(search => search.id)
}))
.catch(error => console.log(error))
}
// ------------------ MOVIES ------------------
export const fetchMovie = text => dispatch => {
axios.get(`https://api.themoviedb.org/3/movie/${text}?api_key=API_KEY&append_to_response=videos,credits,recommendations,watch/providers`)
.then(response => dispatch({
type: SPECIFIC_MOVIE,
payload: response.data.results
}))
.catch(error => console.log(error))
}
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import App from './App'
import reportWebVitals from './reportWebVitals';
import favoritesReducer from './redux/favoritesReducer.js'
import moviesReducer from './redux/moviesReducer.js'
import showsReducer from './redux/showsReducer.js'
import userReducer from './redux/userReducer';
import searchReducer from './redux/searchReducer.js'
const rootReducer = combineReducers({
favorties: favoritesReducer,
movies: moviesReducer,
shows: showsReducer,
users: userReducer,
search: searchReducer
})
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
let store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk)));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
export default
searchReducer.js
const initialState = {
text: '',
movies: [],
loading: false,
movie: []
}
const searchReducer = (state = initialState, {type, payload}) => {
switch (type) {
case 'SEARCH_MOVIE':
return {
...state,
text: payload,
loading: false
};
case 'FETCH_SEARCH':
return {
...state,
movies: payload,
loading: false
};
default:
return state;
}
}
export default searchReducer
movieReducer.js
const initialState = {
text: '',
movie: []
}
const moviesReducer = (state = initialState, {type, payload}) => {
switch (type) {
case 'SPECIFIC_MOVIE':
return {
...state,
movie: payload
};
default:
return state;
}
}
export default moviesReducer
MoviePage.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchMovie } from '../../actions/searchActions';
export class Movie extends Component {
componentDidMount() {
this.props.fetchMovie(this.props.match.params.id);
}
render() {
const { movie } = this.props;
let movieInfo = (
<div className="container">
<img src={movie.Poster} className="thumbnail" alt="Poster" />
<h2 className="mb-4">{movie.Title}</h2>
<li>Genre:</li> {movie.Genre}
<li>Released:</li> {movie.Released}
<li>Rated:</li> {movie.Rated}
<li>IMDB Rating:</li> {movie.imdbRating}
<li>Director:</li> {movie.Director}
<li>Writer:</li> {movie.Writer}
<li>Actors:</li> {movie.Actors}
<h3>About </h3>
{movie.Plot}
</div>
);
return <div>{}</div>;
}
}
const mapStateToProps = state => ({
movie: state.movies.movie
});
export default connect(mapStateToProps,{ fetchMovie })(Movie);
You can access the current state tree of your application using getState method inside of your action creator.
export const fetchMovie = text => (dispatch, getState) => {
console.log(getState()); // you can see the info about your state tree here
axios.get(`https://api.themoviedb.org/3/movie/${text}?api_key=API_KEY&append_to_response=videos,credits,recommendations,watch/providers`)
.then(response => dispatch({
type: SPECIFIC_MOVIE,
payload: response.data.results
}))
.catch(error => console.log(error))
}
Related
I am creating a react app with redux for state management, I am facing issues when trying to dispatch and action, action is showing in redux devtools but it's not storing data to redux store not sure why it's happening, very unusual
If anyone knows why this happens please do let me know
My component is below
import axios from "axios";
import React, { Component } from "react";
import { connect } from "react-redux";
import { SETDATA } from "./store";
class Hello extends Component {
constructor() {
super();
this.state = {
data: [],
};
}
componentDidMount() {
this.firstdispatch();
}
firstdispatch = () => {
axios.get("https://jsonplaceholder.typicode.com/users").then((r) => {
console.log("data fetched", r.data);
this.props.setdata(r.data);
});
};
render() {
return (
<div>
{" "}
fff
{/* <button onClick={this.props.setdata}>getdata</button>
<button onClick={this.props.removedata}>decriment</button> */}
{/* <button onClick={props.push}>push</button>
<button onClick={props.pop}>pop</button> */}
{console.log(this.props)}
{this.props.users &&
this.props.users.map((m, i) => (
<div key={i}>
{m.title} {` - - - -`} {m.email}
</div>
))}
</div>
);
}
}
const mapstatetoprops = (state) => {
return {
users: state.users.users || [],
};
};
const mapDispatchTopProps = (dispatch) => {
return {
setdata: (users) => {
dispatch({ type: SETDATA, users });
},
};
};
export default connect(mapstatetoprops, mapDispatchTopProps)(Hello);
Actions reducers and store is below
updated
import * as redux from "redux";
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
export const SETDATA = "users";
export const DELETEDATA = "data/deletedata";
const initSst = {
users: [],
};
const users = (state = initSst, action) => {
switch (action.type) {
case SETDATA:
return { ...state, ...action.data };
case DELETEDATA:
return { data: null };
default:
return state;
}
};
const rootReducer = redux.combineReducers({
users,
});
const store = redux.createStore(
rootReducer,
composeWithDevTools(
redux.applyMiddleware(thunk)
// other store enhancers if any
)
);
export default store;
Just update "SETDATA" to SETDATA in the switch/case
case SETDATA:
return { ...state, ...action.data };
once I updated the initial state to empty array its working
redux, actions, store
import * as redux from "redux";
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
export const SETDATA = "users";
export const DELETEDATA = "data/deletedata";
const users = (state = [], action) => {
switch (action.type) {
case SETDATA:
return [...action.payload];
default:
return state;
}
};
const rootReducer = redux.combineReducers({
users: users,
});
const store = redux.createStore(
rootReducer,
composeWithDevTools(
redux.applyMiddleware(thunk)
// other store enhancers if any
)
);
export default store;
component
import axios from "axios";
import React, { Component } from "react";
import { connect } from "react-redux";
import { SETDATA } from "./store";
class Hello extends Component {
constructor() {
super();
this.state = {
data: [],
};
}
componentDidMount() {
this.firstdispatch();
}
firstdispatch = async () => {
await axios.get("https://jsonplaceholder.typicode.com/users").then((r) => {
// console.log("data fetched", r.data);
this.props.setdata(r.data);
});
};
render() {
return (
<div>
fff {console.log(this.props.users, "fff")}
{(this.props.users || []).map((m, i) => (
<div key={i}>
{m.title} {m.email}
</div>
))}
</div>
);
}
}
const mapstatetoprops = (state) => {
return {
users: state.users,
};
};
const mapDispatchTopProps = (dispatch) => {
return {
setdata: (users) => {
dispatch({ type: SETDATA, payload: users });
},
};
};
export default connect(mapstatetoprops, mapDispatchTopProps)(Hello);
I am studying redux-saga and I want to fetch data from :
https://jsonplaceholder.typicode.com/posts
and in my redux folder I have the fallowing:
(it can be checked in this github repository
https://github.com/jotasenator/redux-saga-fetching-example/tree/main/src)
\src\redux\api.js
import axios from 'axios'
export const loadPostApi = async () => {
await axios.get(`https://jsonplaceholder.typicode.com/posts`)
}
the get request to the address in question
src\redux\app.actions.js
export const loadPostStart = () => ({
type: 'LOAD_POST_START',
})
export const loadPostSuccess = (posts) => ({
type: 'LOAD_POST_SUCCESS',
payload: posts,
})
export const loadPostFail = (error) => ({
type: 'LOAD_POST_FAIL',
payload: error,
})
those are the actions functions
src\redux\app.reducer.js
const INITIAL_STATE = {
loading: false,
posts: [],
errors: null,
}
export const appReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'LOAD_POST_START':
return {
...state,
loading: true,
}
case 'LOAD_POST_SUCCESS':
return {
...state,
posts: action.payload,
loading: false,
}
case 'LOAD_POST_FAIL':
return {
...state,
errors: action.payload,
loading: false,
}
default:
return state;
}
}
the reducer of the fetching, updating state,
src\redux\counterReducer.js
import { types } from "./types";
const initialState = {
value: 0
}
export const counterReducer = (state = initialState, action) => {
switch (action.type) {
case types.adicionar:
return {
...state,
value: state.value + 1
}
case types.resetear:
return {
...state,
value: 0
}
case types.restar:
return {
...state,
value: state.value - 1
}
default:
return state
}
}
this is the reducer of the counter app, with different approach, types are isolated in another file
src\redux\rootReducer.js
import { combineReducers } from 'redux'
import { counterReducer } from './counterReducer'
import { appReducer } from './app.reducer'
export const rootReducer = combineReducers({
counterReducer,
appReducer
})
the rootReducer for gathering the reducers
src\redux\sagas.js
import { put, takeLatest, call } from 'redux-saga/effects'
import { loadPostApi } from './api'
import { loadPostFail, loadPostSuccess } from './app.actions'
export function* onLoadPostStartAsync() {
try {
const response = yield call(loadPostApi)
yield put(loadPostSuccess(response.data))
} catch (error) {
yield put(loadPostFail(error))
}
}
export function* onLoadPost() {
yield takeLatest('LOAD_POST_START', onLoadPostStartAsync)
}
export default function* rootSaga() {
yield ([
onLoadPost(),
])
}
saga onLoadPostStartAsync called by saga onLoadPost inside rootSaga
src\redux\store.js
import { applyMiddleware, compose, createStore } from "redux";
import createSagaMiddleware from 'redux-saga'
import { rootReducer } from "./rootReducer";
import rootSaga from "./sagas";
const sagaMiddleware = createSagaMiddleware()
const composeEnhancers = (typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose
const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware))
export const store = createStore(rootReducer, enhancer)
sagaMiddleware.run(rootSaga)
this is the store with the redux_devtool_extension, the reducers, and running rootSaga
src\redux\types.js
export const types = {
adicionar: 'ADICIONAR',
resetear: 'RESETEAR',
restar: 'RESTAR'
}
those are the types of the counterApp reducer
src\Counter.js
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
export const Counter = () => {
const dispatch = useDispatch()
const { value } = useSelector(state => state.counterReducer)
const handleAdicionar = () => {
dispatch({ type: 'ADICIONAR' })
}
const handleResetear = () => {
(value !== 0) && dispatch({ type: 'RESETEAR' })
}
const handleRestar = () => {
dispatch({ type: 'RESTAR' })
}
console.log(value)
return (
<div>
<button onClick={handleAdicionar}>Adicionar</button>
{' '}
<button onClick={handleResetear}>Resetear</button>
{' '}
<button onClick={handleRestar}>Restar</button>
<hr />
</div>
)
}
this is the Counter component, it works ok
src\Fetching.js
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { loadPostStart } from './redux/app.actions'
export const Fetching = () => {
const dispatch = useDispatch()
const fetchPost = () => {
dispatch(loadPostStart())
}
const state = useSelector(state => state.appReducer)
console.log(state)
return (
<>
<h1>Fetching from https://jsonplaceholder.typicode.com</h1>
<button onClick={fetchPost}>Fetching</button>
{
!state.loading && state.posts.map((post) => (
<li key={post.id}><h2>{post.title}</h2></li>
))
}
</>
)
}
the Fetching component click on the button calls fetchPost function who dispatch loadPostStart() function which is the same of dispatching {type: 'LOAD_POST_START'}, but nothing happens here when clicking, not fetch nothing from here https://jsonplaceholder.typicode.com/posts
src\index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { store } from './redux/store';
import { Provider } from "react-redux";
import { Unificator } from './Unificator';
ReactDOM.render(
<Provider store={store}>
<Unificator />
</Provider>,
document.getElementById('root')
);
component Unificator has Counter and Fetching component
src\Unificator.js
import React from 'react'
import { Counter } from './Counter'
import { Fetching } from './Fetching'
export const Unificator = () => {
return (
<div>
<Counter />
<Fetching />
</div>
)
}
as you can see is about of two reducers, one is the famous counter, and the another one is the fetching issue, do not know what is happening that is not fetching the data
obviously, i am doing something wrong here...don t see where
Axio returns promise, You need to capture that and return. Please try replacing below code.
export const loadPostApi = async () => {
await axios.get(`https://jsonplaceholder.typicode.com/posts`)
.then((response) => {
console.log('Response', response);
return response;
})
.catch((error) => {
console.log('error', error);
})
}
does anyone knows why nothing happens when the button is clicked?
im trying to fetch the movielist from the server when the button is clicked but it doesnt even shows that the action is working the way i expected to be.
my react index js
import React from 'react';
import { connect } from 'react-redux';
import { getMovies } from '../../actions/movieActions';
const Home = ({ movie }) => {
const handleClick = () => {
getMovies();
};
return (
<div>
<button onClick={() => handleClick()}>Get Movies</button>
</div>
);
};
const mapStateToProps = state => ({
movie: state.movie.movies
});
export default connect(mapStateToProps, { getMovies })(Home);
my rootReducer
import { combineReducers } from 'redux';
import movieReducer from './movieReducer';
export default combineReducers({
movie: movieReducer
// log is what we are calling our state
});
my movie reducer
import { GET_MOVIES } from '../actions/types';
const initialState = {
movies: null
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_MOVIES:
return {
...state,
movies: action.payload
};
default:
return state;
}
};
my movie actions
import { GET_MOVIES } from './types';
// get movies from server
export const getMovies = () => async dispatch => {
try {
const res = await fetch('http://localhost:5000/api/movies');
const data = await res.json();
dispatch({
type: GET_MOVIES,
payload: data
});
} catch (error) {
console.log(error);
}
};
my types.js
export const GET_MOVIES = 'GET_MOVIES';
my store.js
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
Your error is in calling the action directly, instead of the connected getMovies prop here:
const handleClick = () => {
getMovies();
};
Should be:
const handleClick = () => {
this.props.getMovies();
};
The problem is:
I'm trying to use redux-saga in my react app, but i still has this error: Actions must be plain objects. Use custom middleware for async actions. Code it seems correct but no idea why gives that error. I'll be glad for all the help. I'm fighting with it for about two days and still doesn't have a solution. I tried to look up, but I still have this error.
action...
import { GET_DISTRICTS} from '../../constants';
const getAdres = async (url) => {
let response = await fetch(url);
let data = await response.json();
let list = [];
data.AdresList.Adresler.Adres.forEach((item) => {
console.info(item);
list.push({
label: item.ADI,
value: item.ID
});
});
return list;
};
export const actions = {
handleGetDistrictsData: async () => {
let districts = await getAdres(`url is here`);
return {
type: GET_DISTRICTS,
payload: districts
};
},
reducer...
import { GET_DISTRICTS } from '../../constants';
export const initialState = {
districts: [],
quarters: [],
streets: [],
doors: [],
districtSelected: false,
districtSelectedID: null,
quarterSelected: false,
quarterSelectedID: null,
streetSelected: false,
streetSelectedID: null,
doorSelected: false,
doorSelectedID: null
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_DISTRICTS:
return {
...state,
districts: action.payload
};
default:
return state;
}
};
component...
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { actions as addressActions } from '../../../../redux/actions/address';
import Select from 'react-select';
const Districts = (props) => {
let [ fetchedData, setFetchedData ] = useState(false);
useEffect(() => {
props.handleGetDistrictsData();
setFetchedData(true);
});
return (
<React.Fragment>
<Select
name='adresSelect'
options={props.address.districts}
onChange={props.handleDistrictChange}
placeholder='Please Select'
/>
</React.Fragment>
);
};
const mapStateToProps = (state) => ({
address: state.address
});
const mapDispatchToProps = function(dispatch) {
return bindActionCreators({ ...addressActions }, dispatch);
};
export default connect(mapStateToProps, mapDispatchToProps)(Districts);
-------------
import React from 'react';
import Districts from './Districts';
const AddressSearchWidget = (props) => {
return (
<React.Fragment>
<Districts />
</React.Fragment>
);
};
export default AddressSearchWidget
store...
import { applyMiddleware, combineReducers, compose, createStore } from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootSaga from './sagas/index';
import * as reducers from './';
export function initStore() {
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const rootReducer = combineReducers(reducers);
const sagaMiddleware = createSagaMiddleware();
const store = createStore(rootReducer, composeEnhancer(applyMiddleware(sagaMiddleware)));
// Run sagas
sagaMiddleware.run(rootSaga);
return store;
}
handleGetDistrictsData returns a promise (all async functions return promises). You cannot dispatch a promise in plain redux saga, and redux-saga does not change this. Instead, dispatch a normal action, and have that action run a saga. The saga can then do async things, and when it's done dispatch another action. The reducer listens only for that second action.
// Actions:
export const getDistrictsData = () => ({
type: GET_DISTRICTS,
})
export const districtsDataSuccess = (districts) => ({
type: DISTRICTS_DATA_SUCCESS,
payload: districts
})
// Sagas:
export function* watchGetDistricts () {
takeEvery(GET_DISTRICTS, getDistricts);
}
function* getDistricts() {
let response = yield fetch(url);
let data = yield response.json();
let list = [];
data.AdresList.Adresler.Adres.forEach((item) => {
console.info(item);
list.push({
label: item.ADI,
value: item.ID
});
});
yield put(districtsDataSuccess(list));
}
// reducer:
export default (state = initialState, action) => {
switch (action.type) {
case DISTRICTS_DATA_SUCCESS:
return {
...state,
districts: action.payload
};
default:
return state;
}
};
I am getting a dispatch is not defined error from 'shopfront' code. I believe it is because i'm not passing the properties down to the next level but I'm not sure if that is correct or not. I want to be able to pass the dispatch function through to the product.actions code correctly.
I have tried to narrow down the problem as much as possible by removing unnecessary code. I have a user reducer that is working correctly but I don't know why this product reducer isn't
// products.reducer
const initialState = {
products: null,
error: null
};
const ProductReducer = (state = initialState, action) => {
let newState = null;
switch(action.type){
case "GET_ALL_PRODUCTS": newState = {
...state,
products: action.products
};
return newState;
case "GET_ALL_PRODUCTS_FAIL": newState = {
...state,
error: action.error
};
return newState;
default: return state;
}
};
export default ProductReducer;
// index
import React from "react";
import ReactDOM from "react-dom";
import "bootstrap/dist/css/bootstrap.min.css";
import { register } from "./serviceWorker";
import { createStore, combineReducers, applyMiddleware, compose } from "redux";
import { Provider } from "react-redux";
import thunk from "redux-thunk";
import UserReducer from "./store/reducers/users.reducers";
import ProductReducer from "./store/reducers/products.reducer";
import "./index.css";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
const appReducer = combineReducers({
usersRed: UserReducer,
productsRed: ProductReducer
});
const logger = (store) => {
return next => {
return action => {
console.log("Middleware dispatching ");
console.log(action);
const result = next(action);
console.log("Middleware next state ");
console.log(store.getState());
return result;
};
};
};
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const appStore = createStore(appReducer, composeEnhancers(applyMiddleware(logger, thunk)));
const app = (
<BrowserRouter>
<Provider store={appStore}>
<App />
</Provider>
</BrowserRouter>
);
ReactDOM.render(app, document.getElementById("root"));
register();
// shopfront
import React, { Component } from "react";
import { Container, Row, Col, InputGroup, InputGroupAddon, Button } from "reactstrap";
import { withRouter } from "react-router-dom";
import { connect } from "react-redux";
import { Alert } from "reactstrap";
import * as actionMethods from "../../store/actions/index.actions";
import Product from "../../components/Product/Product";
class Shopfront extends Component {
state = {
onAlert: false,
internalError: null
};
componentDidMount() {
this.props.loadAllProducts(5);
console.log("component_did_mount_run")
}
render() {
let ProductsList = <h1>No Products Yet!</h1>;
if (this.props.products !== null) {
ProductsList = this.props.products.map(Product => {
return <Product
key={Product.id}
title={Product.name}
excerpt={Product.description}
medialink={Product.permalink}
ProductId={Product.id}
/>;
});
}
return (
<Container>
{ProductsList}
</Container>
);
}
};
const mapStateToProps = (state) => {
return {
products: state.productsRed.products,
error: state.productsRed.error
};
};
const mapDispatchToProps = (dispatch) => {
return {
loadAllProducts: (perpage) => { dispatch(actionMethods.loadAllProducts(perpage)) }
};
};
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Shopfront));
// index.actions
export {
loadAllProducts
} from "./product.actions";
// product.actions
import wcapi from "../../axios-wp";
export const loadAllProducts = (perpage) => {
wcapi.get("products", {
per_page: perpage,
})
.then((response) => {
// Successful request
let productsRes = response.data;
dispatch({ type: "GET_ALL_PRODUCTS", products: productsRes });
})
.catch((err) => {
// Invalid request, for 4xx and 5xx statuses
dispatch({ type: "GET_ALL_PRODUCTS_FAIL", error: err });
})
}
Thank you for your help!
it is because you are not returning dispatch from your loadAllProducts action
/ product.actions
import wcapi from "../../axios-wp";
export const loadAllProducts = (perpage) => (dispatch) => { //make this change
wcapi.get("products", {
per_page: perpage,
})
.then((response) => {
// Successful request
let productsRes = response.data;
dispatch({ type: "GET_ALL_PRODUCTS", products: productsRes });
})
.catch((err) => {
// Invalid request, for 4xx and 5xx statuses
dispatch({ type: "GET_ALL_PRODUCTS_FAIL", error: err });
})
}
I have ever faced such this issue before. Then I used return dispatch => {} like this:
export const loadAllProducts = perpage => {
return dispatch => {
wcapi.get("products", {
per_page: perpage,
})
.then((response) => {
// Successful request
let productsRes = response.data;
dispatch({ type: "GET_ALL_PRODUCTS", products: productsRes });
})
.catch((err) => {
// Invalid request, for 4xx and 5xx statuses
dispatch({ type: "GET_ALL_PRODUCTS_FAIL", error: err });
})
}
}