Here is my code related to reducers and actions but I do not see my storeState getting data. It adds new item in stateStore but that is undefined. My actions/index file is as following
import axios from 'axios';
const API_KEY = 'f41c2a072e2fab2d1319257e842d3b4b';
const ROOT_URL = `https://api.openweathermap.org/data/2.5/forecast?appid=${API_KEY}`;
export const FETCH_WEATHER = 'FETCH_WEATHER';
export function fetchWeather(city){
const url = `${ROOT_URL}&q=${city},pk`;
const request = axios.get(url);
return {
type: FETCH_WEATHER,
payload: request
};
}
My weather_reducer.js is as follows
import { FETCH_WEATHER } from '../actions/index';
export default function(state=[], action){
switch (action.type) {
case FETCH_WEATHER:{
return [action.payload.data, ...state]; //ES6 Spread Operator [newCity,all_old_elms_from_state]
}
default:
return state;
}
}
my reducers/index.js is as follows:-
import { combineReducers } from 'redux';
import WeatherReducer from './reducer_weather';
const rootReducer = combineReducers({
weather: WeatherReducer
});
export default rootReducer;
My index.js file is as following
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import rootReducer from './reducers/index';
import './index.css';
import 'bootstrap/dist/css/bootstrap.min.css';
import App from './components/App';
const store = createStore(rootReducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'));
and Lastly my contianers/SearchBar.js is as follows
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { fetchWeather } from '../actions/index';
class SearchBar extends Component{
constructor(props){
super(props);
this.state = {term:''}; //Local state of component ======== Not Redux(Global APP) state
this.onInputChange = this.onInputChange.bind(this); //Bind this (components context to your function
this.onFromSubmit = this.onFromSubmit.bind(this);
}
onInputChange(event) {
this.setState({ term:event.target.value });
}
onFromSubmit(event){
event.preventDefault();
//Go fetch weather data based on term
this.props.fetchWeather(this.state.term);
this.setState({ term:'' });
}
render(){
return(
<div className="col-sm-12">
<form onSubmit={this.onFromSubmit}>
<div className="input-group mb-3">
<input
className="form-control"
placeholder="Get five-day forecast for your favorite cities.."
value={this.state.term}
onChange={this.onInputChange}
/>
<div className="input-group-append">
<button className="btn btn-outline-secondary" type="submit">Submit</button>
</div>
</div>
</form>
</div>
);
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators({ fetchWeather },dispatch);
}
export default connect(null,mapDispatchToProps)(SearchBar);
Thank you for the support, I figured it out, actually I was missing the middleware parameter in my index.js. As in applyMiddleWare we have to pass the middleware name.
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware } from 'redux';
import ReduxPromise from 'redux-promise';
import { Provider } from 'react-redux';
import rootReducer from './reducers/index';
import './index.css';
import 'bootstrap/dist/css/bootstrap.min.css';
import App from './components/App';
const storeWithMiddleWare = applyMiddleware(ReduxPromise)(createStore);
ReactDOM.render(
<Provider store={storeWithMiddleWare(rootReducer)}>
<App />
</Provider>,
document.getElementById('root'));
Related
I have the following modules for my React app:
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import MainApp from './MainApp';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<MainApp />
</React.StrictMode>,
document.getElementById('root')
);
MainApp.js
import React from 'react';
import App from './containers/app';
import './App.css';
import {ConnectedRouter} from 'connected-react-router'
import {Provider} from 'react-redux';
import {Route, Switch} from 'react-router-dom';
import configureStore, {history} from './store';
export const store = configureStore();
const MainApp = () =>
<Provider store={store}>
<ConnectedRouter history={history}>
<Switch>
<Route path="/" component={App}/>
</Switch>
</ConnectedRouter>
</Provider>;
export default App;
store/index.js
import {applyMiddleware, compose, createStore} from 'redux';
import reducers from '../reducers/index';
import {createBrowserHistory} from 'history'
import {routerMiddleware} from 'connected-react-router';
import createSagaMiddleware from 'redux-saga';
import rootSaga from '../sagas/index';
const history = createBrowserHistory();
const routeMiddleware = routerMiddleware(history);
const sagaMiddleware = createSagaMiddleware();
const middlewares = [sagaMiddleware, routeMiddleware];
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
export default function configureStore(initialState) {
const store = createStore(reducers(history), initialState,
composeEnhancers(applyMiddleware(...middlewares)));
sagaMiddleware.run(rootSaga);
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../reducers/index', () => {
const nextRootReducer = require('../reducers/index');
store.replaceReducer(nextRootReducer);
});
}
return store;
}
export {history};
containers/dataviewer/index.js
import React from 'react';
import {connect} from 'react-redux';
import {
connectPath
} from '../../actions/Paths';
import Button from '#material-ui/core/Button';
import ButtonGroup from '#material-ui/core/ButtonGroup';
import TabularViewer from './TabularViewer';
import CollapsableViewer from './CollapsableViewer';
import ChartViewer from './ChartViewer';
class DataViewer extends React.Component {
constructor() {
super();
this.state = {
displayStyle: null
}
this.handleClick = this.handleClick.bind(this);
}
handleClick = (style) => {
this.setState({displayStyle: style})
}
DisplayControllers = () => {
return (
<ButtonGroup variant="text" color="primary" aria-label="outlined primary button group">
<Button color={this.state.displayStyle === 'tabular'? 'secondary': 'primary'} onClick={() => this.handleClick('tabular')}>Tabular</Button>
<Button color={this.state.displayStyle === 'collapsable'? 'secondary': 'primary'}onClick={() => this.handleClick('collapsable')}>Colapsable</Button>
<Button color={this.state.displayStyle === 'chart'? 'secondary': 'primary'} onClick={() => this.handleClick('chart')}>Gráfico</Button>
</ButtonGroup>
)
}
DisplayComponent = () => {
switch (this.state.displayStyle) {
case 'tabular':
return <TabularViewer />
case 'collapsable':
return <CollapsableViewer />
case 'chart':
return <ChartViewer />
default:
return <p>Seleccione un estilo de desplegado.</p>;
}
}
render() {
return (
<div>
<p>Data Viewer</p>
<this.DisplayControllers />
<this.DisplayComponent />
</div>
)
}
}
const mapStateToProps = ({paths}) => {
const {connection, path, data} = paths;
return {
connection,
path,
data
}
}
export default connect(mapStateToProps, {connectPath})(DataViewer)
Te problem is that I get the following error when the component is mounted:
Error: Could not find "store" in the context of "Connect(DataViewer)". Either wrap the root component in a <Provider>, or pass a custom React context provider to <Provider> and the corresponding React context consumer to Connect(DataViewer) in connect options.
I can't see what I am missing.
EDIT
containers/app.js
import React from 'react';
import DataViewer from './DataViewer';
class App extends React.Component {
render() {
return (
<div className="App">
<header className="App-header">
<DataViewer
database_host="127.0.0.1'"
database_port="5432"
database_name="TCDigital"
database_user="postgres"
database_password="xxxx"
path="Ventas"
displayStyle="collapsable"
/>
</header>
</div>
);
}
}
export default App;
I am trying to populate my redux store state with a list of genres from an API, I can get the action to dispatch to the reducer, but the reducer does not seem to update the state because my console.log in src/components/MovieForm.tsx returns the default state of "null" instead of the array of genres and I do not know why, I am trying to see if the state is updated in src/components/MovieForm.tsx in the setInterval where I am logging the state, maybe the problem is how I am accessing the state? Here are the files:
src/actions/movieActions.ts:
import { ActionCreator, Dispatch } from 'redux';
import { ThunkAction } from 'redux-thunk';
import { IMovieState } from '../reducers/movieReducer';
import axios from 'axios';
export enum MovieActionTypes {
ANY = 'ANY',
GENRE = 'GENRE',
}
export interface IMovieGenreAction {
type: MovieActionTypes.GENRE;
genres: any;
}
export type MovieActions = IMovieGenreAction;
/*<Promise<Return Type>, State Interface, Type of Param, Type of Action> */
export const movieAction: ActionCreator<ThunkAction<Promise<any>, IMovieState, void, IMovieGenreAction>> = () => {
return async (dispatch: Dispatch) => {
try {
console.log('movieActions called')
let res = await axios.get(`https://api.themoviedb.org/3/genre/movie/list?api_key=${process.env.REACT_APP_MOVIE_API_KEY}&language=en-US`)
console.log(res.data.genres)
dispatch({
genres: res.data.genres,
type: MovieActionTypes.GENRE
})
} catch (err) {
console.log(err);
}
}
};
src/reducers/movieReducer.ts:
import { Reducer } from 'redux';
import { MovieActionTypes, MovieActions } from '../actions/movieActions';
export interface IMovieState {
//property: any;
genres: any;
}
const initialMovieState: IMovieState = {
//property: null,
genres: null,
};
export const movieReducer: Reducer<IMovieState, MovieActions> = (
state = initialMovieState,
action
) => {
switch (action.type) {
case MovieActionTypes.GENRE: {
console.log('MovieActionTypes.GENRE called')
return {
genres: action.genres,
};
}
default:
console.log('Default action called')
return state;
}
};
src/store/store.ts:
import { applyMiddleware, combineReducers, createStore, Store } from 'redux';
import thunk from 'redux-thunk';
import { IMovieState, movieReducer } from '../reducers/movieReducer';
// Create an interface for the application state
export interface IAppState {
movieState: IMovieState
}
// Create the root reducer
const rootReducer = combineReducers<IAppState>({
movieState: movieReducer
});
// Create a configure store function of type `IAppState`
export default function configureStore(): Store<IAppState, any> {
const store = createStore(rootReducer, undefined, applyMiddleware(thunk));
return store;
}
src/components/MovieForm.tsx (the file that is supposed to dispatch the action):
import React, { useState } from 'react';
import { makeStyles } from '#material-ui/core/styles';
import Paper from '#material-ui/core/Paper';
import Box from '#material-ui/core/Box';
import Select from '#material-ui/core/Select';
import MenuItem from '#material-ui/core/MenuItem';
import { spacing } from '#material-ui/system';
import Card from '#material-ui/core/Card';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import { CardHeader, TextField, CircularProgress } from '#material-ui/core';
import { useDispatch, useSelector } from 'react-redux';
import { movieAction } from '../actions/movieActions';
import { IAppState } from '../store/store';
import axios from 'axios';
const MovieForm = () => {
const dispatch = useDispatch()
const getGenres = () => {
console.log('actions dispatched')
dispatch(movieAction())
}
const genres = useSelector((state: IAppState) => state.movieState.genres);
//const [genreChoice, setGenreChoice] = useState('')
return (
<>
<h1>Movie Suggester</h1>
<Paper elevation={3}>
<Box p={10}>
<Card>
<div>Hello World. </div>
<Select onChange={() => console.log(genres)}>
<MenuItem>
hello
</MenuItem>
<br />
<br />
</Select>
<Button onClick={() => {
getGenres()
setTimeout(function(){
console.log(genres)
}, 5000)
}}>genres list</Button>
<Button onClick={() => console.log(axios.get(`https://api.themoviedb.org/3/discover/movie?api_key=${process.env.REACT_APP_MOVIE_API_KEY}&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&with_genres=35&page=1`))}>Click me</Button>
</Card>
</Box>
</Paper>
</>
)
}
export default MovieForm
and here is the src/index.tsx in case the problem is here and I'm unaware:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import { BrowserRouter as Router } from 'react-router-dom';
import './index.css';
import CssBaseline from '#material-ui/core/CssBaseline';
import { ThemeProvider } from '#material-ui/core/styles';
import theme from './theme';
import App from './App';
import configureStore from './store/store';
const store = configureStore();
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<ThemeProvider theme={theme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<Router>
<App />
</Router>
</ThemeProvider>
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
Thanks for taking a look at this and and attempting to help me see what I am unable to!
I'm currently trying to resolve a promise from axios and get data with Redux-Promise Middleware.I'have already registered Redux-Promise middleware.In the search_bar.js, fetchWeather Action Creator is called.It creates the axio promise and passes to the reducers.In reducer_weather.js , the promise isn't still resolved.Can someone help me with this?
index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import ReduxPromise from "redux-promise";
import App from "./components/app";
import reducers from "./reducers";
const createStoreWithMiddleware = applyMiddleware(ReduxPromise)(createStore);
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<App />
</Provider>,
document.querySelector(".container")
);
reducers/index.js
import { combineReducers } from "redux";
import WeatherReducer from "./reducer_weather";
const rootReducer = combineReducers({
weather: WeatherReducer
});
export default rootReducer;
reducers/reducer_weather.js
export default function(state = null, action){
console.log("Reducers");
console.log(action);
return state;
}
containers/search_bar.js
import React,{Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {fetchWeather} from '../actions/index';
class SearchBar extends Component {
constructor(props){
super(props);
this.state = {
term:''
};
this.onInputChange = this.onInputChange.bind(this);
this.onFormSubmit = this.onFormSubmit.bind(this);
}
onInputChange(e){
this.setState({term:e.target.value});
}
onFormSubmit(e){
e.preventDefault();
this.props.fetchWeather(this.state.term);
this.setState({term:''})
}
render(){
return (
<form className="input-group" onSubmit={this.onFormSubmit}>
<input value={this.state.term} onChange={this.onInputChange}/>
<span className="input-group-btn">
<button type="submit" className="btn btn-secondary">submit</button>
</span>
</form>
);
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators({fetchWeather},dispatch);
}
export default connect(null,mapDispatchToProps)(SearchBar);
actions/index.js
import { request } from "http";
import axios from 'axios';
const API_KEY = "eba56fc1ee64a90231051880ed9788d6";
const ROOT_URL = `http://api.openweathermap.org/data/2.5/forecast?appid=${API_KEY}`;
export function fetchWeather(city){
const url = `${ROOT_URL}&q=${city},us`;
const request = axios.get(url);
console.log("Actions");
console.log(request);
return {
type: fetchWeather,
payload: request
};
}
Your code to create the redux store looks quite odd to me. How I setup the store:
import { createStore, applyMiddleware } from 'redux';
import promiseMiddleware from 'redux-promise';
const store = createStore(
rootReducer,
applyMiddleware(promiseMiddleware)
);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.querySelector(".container")
);
Also, what's the implementation of bindActionCreators(...)?
Struggling to dispatch an action from my React component. This is my first Redux app. Everything seems to be working fine, but if it was I would not be posting this question. I am using Redux devTool to debug my app. If I use the dispatcher from the devTools my reducer is triggered with no problem. However I am unable to dispatch the same action from my React components. I added a breakpoint in my action to see if it was being triggered. I definately is and it is also returning a valid action (type & payload). Here is my code:
store.js
import {createStore, applyMiddleware, compose} from 'redux'
import {createLogger} from 'redux-logger'
import thunk from 'redux-thunk'
import promise from 'redux-promise-middleware'
import reducers from './reducers/index'
const logger = createLogger()
const store = createStore(
reducers,
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__(),
compose(
applyMiddleware(thunk, promise, logger)
)
)
export default store
reducers (index.js)
import {combineReducers} from 'redux'
import userReducer from './userReducer'
const allReducers = combineReducers({
user: userReducer
})
export default allReducers
client.js
import React from 'react'
import ReactDOM from 'react-dom'
import {Provider} from 'react-redux'
import store from './store'
import Router from './modules/router'
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
import getMuiTheme from 'material-ui/styles/getMuiTheme'
import CustomTheme from './modules/theme'
import injectTapEventPlugin from 'react-tap-event-plugin'
require('../scss/style.scss')
// Needed for onTouchTap
// http://stackoverflow.com/a/34015469/988941
injectTapEventPlugin();
ReactDOM.render(
<Provider store={store}>
<MuiThemeProvider muiTheme={CustomTheme}>
<Router/>
</MuiThemeProvider>
</Provider>,
document.getElementById('app')
);
userReducer.js
export default function (state = {loggedIn: false}, action) {
console.log("THIS IS THE REDUCER: STATE: ", state, " - ACTION: ", action)
switch (action.type) {
case 'LOGIN':
return {...state, loggedIn: action.payload}
}
return state;
}
userActions.js
export const login = () => {
console.log("TEST")
return {
type: 'LOGIN',
payload: true
}
}
login.js
import React from 'react'
import ReactDOM from 'react-dom'
import LoginForm from '../containers/loginform'
class Login extends React.Component {
render() {
return (
<LoginForm/>
)
}
}
export default Login
loginform.js
import React, {PropTypes} from 'react'
import ReactDOM from 'react-dom'
import {Redirect} from 'react-router-dom'
import {connect} from 'react-redux'
import {login} from '../actions/userActions'
import RaisedButton from 'material-ui/RaisedButton'
import TextField from 'material-ui/TextField'
class LoginForm extends React.Component {
constructor(props) {
super(props)
}
loginReq(e){
e.preventDefault()
this.props.login()
}
render() {
return (
<div>
<form className='login-form-container' onSubmit= {this.loginReq.bind(this)}>
<div className='login-form-row'>
<TextField
ref='email'
hintText='Email'
floatingLabelText='Email'
className='login-form-field'/>
</div>
<div className='login-form-row'>
<TextField
ref='password'
hintText='Password'
floatingLabelText='Password'
type='password'
className='login-form-field'/>
</div>
<div className='login-form-row'>
<RaisedButton
type= 'submit'
label='Login'
className='login-form-button'/>
</div>
</form>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
loggedIn: state.user.loggedIn
}
}
const mapDispatchToProps = (dispatch) => {
return {
login: () => dispatch(login())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(LoginForm)
Please could you give me some guidance to how else i can debug to find out why this dispatch is not working. I have tried adding the action object straight into that dispatch function. still no luck. I get no errors in the console nothing. My console.logs are only printed when the view renders and when i click on the login submit button.
Console Screenshot
Finally found my issue. My middleware implementation was causing the issue. I was passing in promise incorrectly. Should be:
import {createStore, applyMiddleware} from 'redux'
import {composeWithDevTools} from 'redux-devtools-extension'
import {createLogger} from 'redux-logger'
import thunk from 'redux-thunk'
import promise from 'redux-promise-middleware'
import reducers from './reducers/index'
const logger = createLogger()
const middleware = applyMiddleware(promise(), logger, thunk)
const store = createStore(reducers, composeWithDevTools(middleware))
export default store
Also found that redux-devtools-extension was cleaner for Redux devTools.
My hunch would be how you are trying to invoke the function to dispatch the action. Firstly, bind the function to this in the component constructor (See the React docs on event handlers here for more info). Secondly, just pass the function to onSubmit.
class LoginForm extends React.Component {
constructor(props) {
super(props)
this.loginReq = this.loginReq.bind(this);
}
loginReq(e) {
e.preventDefault()
this.props.login()
}
render() {
return (
<div>
<form className='login-form-container' onSubmit={this.loginReq}>
<div className='login-form-row'>
<TextField
ref='email'
hintText='Email'
floatingLabelText='Email'
className='login-form-field'/>
</div>
<div className='login-form-row'>
<TextField
ref='password'
hintText='Password'
floatingLabelText='Password'
type='password'
className='login-form-field'/>
</div>
<div className='login-form-row'>
<RaisedButton
type= 'submit'
label='Login'
className='login-form-button'/>
</div>
</form>
</div>
)
}
}
An alternative way to bind the function to this is to remove the bind statement in the constructor and use an arrow function for the form prop, like this:
onSubmit={e => this.loginReq(e)}
modify action:
export const login = () => {
return function (dispatch) {
console.log('here');
dispatch({
type: 'LOGIN',
payload: true
});
}
}
I guess you'd like => syntax in that case.
Going through udemy tutorial and got stuck and for some reason can't figure out what happaned. I went through all my code and it looks right as far as I can tell compared to the tutorial. Code:
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import ReduxPromise from 'redux-promise';
import App from './components/app';
import reducers from './reducers';
const createStoreWithMiddleware = applyMiddleware(ReduxPromise)(createStore);
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<App />
</Provider>
, document.querySelector('.container'));
searchbar.js:
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {fetchWeather} from '../actions/index';
export default class SearchBar extends Component{
constructor(props){
super(props);
this.state = {term: ''}
this.onInputChange = this.onInputChange.bind(this)
}
onInputChange(e){
console.log(e.target.value)
this.setState({
term: e.target.value
})
}
onFormSubmit(e){
e.preventDefault()
}
render(){
return (
<form onSubmit ={this.onFormSubmit} className = "input-group">
< input
placeholder =" Get a forecast"
className = "form-control"
value = {this.state.term}
onChange = {this.onInputChange}
/>
<span className = "input-group-btn">
<button type="submit" className = "btn btn-secondary">Submit </button>
</span>
</form>
);
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators({fetchWeather}, dispatch);
}
export default connect (null, mapDispatchToProps)(SearchBar);
reducers/index.js
import axios from 'axios';
const API_KEY = 'c4c2ff174cb65bad330f7367cc2a36fa'
const ROOT_URL = `http://api.openweathermap.org/data/2.5/forecast?q=appid=${API_KEY}`;
export const FETCH_WEATHER = 'FETCH_WEATHER';
export function fetchWeather(city){
let url = `${ROOT_URL}&q=${city},us`;
let request = axios.get(url);
return {
type: FETCH_WEATHER,
payload: request
};
}
app.js
import React, { Component } from 'react';
import SearchBar from '../containers/search_bar';
export default class App extends Component {
render() {
return (
<div>
<SearchBar />
</div>
);
}
}
To answer,
Your code got a little mixed up, the block that you have in reducers/index.js is your action and should be located in actions/index.js instead. As mentioned you are importing it from there in your searchbar component:
import {fetchWeather} from '../actions/index';
The reducer here should be making use of the FETCH_WEATHER type that you are setting up in your action in order to update the state of the redux store, so something along the lines of:
switch(action.type) {
case FETCH_WEATHER:
return [action.payload.data].concat(state);
}
return state;
Then either export that directly or make use of combineReducers from redux to return a single reducer function if you have more than one.
Link to the always awesome: DOCS