I am trying to learn the react and for that I am trying to create a sample todo app. I have a python flask backend which servers as REST server and react as web server.
Everything works find an I am able to show todos and delete particular todo as well. However now I have started learning Redux, and that seems really confusing.
I am not sure how to make call to my rest server. Following just returns promise, not sure how to get the data, rather than promise.
store.js
import axios from 'axios'
import { createStore } from 'redux'
export const ADD_TODO = 'ADD_TODO'
let nextTodoId = 0
export const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoId++,
text
})
export const listTodo = todos => ({
type: 'LIST_TODO',
todos
})
const add_todo = (id, text) => {
return axios.post("http://localhost:5001/todos", {id:id, data:text})
.then(Response=>{
store.dispatch(addTodo(Response.data));
})
}
const fetch_data = () => {
return axios.get("http://localhost:5001/todos")
.then(Response=>{
store.dispatch(listTodo(Response.data))
})
}
const initialState ={
todos: {},
new_todo: ''
}
function todoApp(state = initialState, action) {
console.log("reducer called...")
switch (action.type) {
case ADD_TODO:
return Object.assign({}, state, {
new_todo: action.text
})
default:
return state
}
}
const store = createStore(todoApp)
export default store
app.js
import React, {Component} from 'react'
import {connect} from 'react-redux'
class App extends Component{
render(){
return(
<div>
<button onClick={this.props.addTodo('testing')}>fetch_Data</button>
</div>
);
}
}
export default connect() (App)
index.js
ReactDOM.render(<Provider store={store}> <App /> </Provider>,
document.getElementById('root'));
Firstly, you should export the actions you have created which will then be imported and used in the components using the connect HOC.
You can dispatch the 'fetch_data' action to get the data in your component. Also, you can dispatch 'addTodo' action to add new todo in the list.
export const ADD_TODO = 'ADD_TODO';
export const GET_TODO = 'GET_TODO';
export const fetch_data = () => {
return (dispatch) => axios.get("http://localhost:5001/todos")
.then(response => {
dispatch({type: GET_TODO, todos: response.data});
})
}
export const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoId++,
text: text
});
Use the actions constants like ADD_TODO, GET_TODO to save or to update the redux state in reducers
const todoApp = (state = initialState, action) => {
console.log("reducer called...")
switch (action.type) {
case ADD_TODO:
const todos = {...state.todos};
todos[action.id] = action.text;
return Object.assign({}, state, {
todos: todos
});
case GET_TODO:
return Object.assign({}, state, {
todos: action.todos
});
default:
return state
}
}
Importing the actions and then call the function you have added in the 'mapDispatchToProps' to dispatch the actions.
import React, {Component} from 'react'
import {connect} from 'react-redux';
import { addTodo, fetch_data } from "../store";
class App extends Component{
render(){
return(
<div>
<button onClick={this.props.addTodo(todoId, 'testing')}>fetch_Data</button>
</div>
);
}
}
const mapStateToProps = (state) => ({
todos: state.todoApp.todos
});
const mapDispatchToProps = (dispatch) => ({
addTodo: (id, text) => dispatch(addTodo(id, text)),
fetch_data: () => dispatch(fetch_data())
});
export default connect(mapStateToProps, mapDispatchToProps)(App);
redux is based on actions and reducers, basically reducers are pure functions which means no side effects as for example api calls, I'd advice you read more about redux and how to use redux with redux-chunk for making api calls
You make this work like this. You need to dispatch action when you have response.
const fetch_data = () => {
return axios.get("http://localhost:5001/todos")
.then(Response=>{
store.dispatch(addTodo(Response.data));
})
}
export const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoId++,
text: text
})
Related
I am supposed to fetch data from an endpoint and display the results using Redux-Sauce.
All is fine except I can't seem to update the state after I fetch data. Read the docs so this is what I could come up with. Please tell me what I am doing wrong!?
How do I update the state calling the action creators inside HomeContainer.js?
Link to codeSandbox
https://codesandbox.io/s/fragrant-sky-56yhi?file=/src/index.js
HomeContainer.js
import React, { useEffect, useState } from "react";
import axios from "axios";
import { connect } from "react-redux";
import Creators from "../redux/Reducers/reducers";
const HomeContainer = ({ iTunesData, actions }) => {
const { loading, data, error } = iTunesData;
const [searchTerm, setSearchTerm] = useState("");
const submitHandler = (e) => {
e.preventDefault();
const getData = async () => {
actions.fetchDataRequest();
try {
const { data } = await axios.get(
`https://itunes.apple.com/search?term=${searchTerm}`
);
// console.log(data);
actions.fetchDataSuccess(data);
} catch (error) {
actions.fetchDataFail(error);
}
};
getData();
// console.log("On submit handler clicked!");
};
// console.log(iTunesData, actions);
// console.log(searchTerm);
// console.log(iTunesData);
console.log(loading, data, error);
return (
<form onSubmit={submitHandler}>
<h1> Home Container</h1>
<input
placeholder="Search..."
type="text"
onChange={(e) => setSearchTerm(e.target.value)}
/>
<button>Go</button>
</form>
);
};
const mapStateToProps = (state, ownProps) => {
return {
iTunesData: state
};
};
const mapDispatchToProps = (state, ownProps) => {
return {
actions: Creators
};
};
export default connect(mapStateToProps, mapDispatchToProps)(HomeContainer);
reducer.js
import { createReducer, createActions } from "reduxsauce";
const { Types, Creators } = createActions({
fetchDataRequest: null,
fetchDataSuccess: ["payload"],
fetchDataFail: ["error"]
});
export default Creators;
const initialState = {
loading: false,
data: [],
error: false
};
export const fetchDataRequest = (state = initialState, action) => {
return { ...state, loading: true, data: [], error: false };
};
export const fetchDataSuccess = (state = initialState, action) => {
return { ...state, data: action.payload, error: false };
};
export const fetchDataFail = (state = initialState, action) => {
return { ...state, data: null, error: action.error };
};
// map our action types to our reducer functions
export const HANDLERS = {
[Types.FETCH_DATA_REQUEST]: fetchDataRequest,
[Types.FETCH_DATA_SUCCESS]: fetchDataSuccess,
[Types.FETCH_DATA_FAIL]: fetchDataFail
};
export const reducer = createReducer(initialState, HANDLERS);
store.js
import { applyMiddleware, combineReducers, compose, createStore } from "redux";
import { reducer } from "./Reducers/reducers";
import thunk from "redux-thunk";
const store = createStore(reducer, applyMiddleware(thunk));
export default store;
index.js
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import store from "./redux/store";
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
</StrictMode>,
rootElement
);
Your mapDispatchToProps is wrong. Written like you want to use it, it would need to acutally bind dispatch to the actions, which you don't.
If you want to use that nested, you will have to call bindActionCreators manually.
const mapDispatchToProps = (dispatch) => {
return {
actions: bindActionCreators(Creators, dispatch)
};
};
Otherwise you could also use the "object notation"
const mapDispatchToProps = Creators
in which case the bound action creators will be available as props.fetchDataSuccess, not props.actions.fetchDataSuccess.
Generally, it is also recommended to not use connect at all with function components, but the React-Redux hooks useSelector and useDispatch.
See https://react-redux.js.org/api/hooks
Also, as for your internship, please forward the official Redux Style Guide to your team, with best regards from a Redux Maintainer ;)
https://redux.js.org/style-guide/style-guide/
We really want them to use the official Redux Toolkit, as it will simplify their code a lot more than Redux-Sauce already does - including allowing for immutable logic in reducers thanks to immer integration and containing a full blown api cache abstraction.
Maybe trying that out and prototyping using it might make for a nice internship project for you in the end ;)
I just tried make simply reducer in react redux but it never called. After a lot trial i have no idea why it's not working. console.log in action is showing but reducer never is called.
import React from "react";
import { connect } from "react-redux";
import * as actions from "store/actions";
function Login(props) {
const login = (e) => {
e.preventDefault();
props.login();
};
return (
<form onSubmit={login}>
<button> login </button>
</form>
);
}
const mapDispatchToProps = (dispatch) => {
return {
login: () => dispatch(actions.login),
};
};
export default connect(null, mapDispatchToProps)(Login);
actions file- i'm here console.log is showing correctly
import * as actionsTypes from "./actionTypes";
export const logout = () => {
return {
type: actionsTypes.AUTH_LOGOUT,
};
};
export const login = () => {
console.log("i'm here")
return {
type: actionsTypes.AUTH_LOGIN,
};
};
reducer
import * as actionTypes from "../actions/actionTypes";
const initialState = {
isLogged: false,
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.AUTH_LOGIN:
return {
...state,
isLogged: true,
};
case actionTypes.AUTH_LOGOUT:
return {
...state,
isLogged: false,
};
default:
return state;
}
};
export default reducer;
many thanks for help
Probably, you forget to make a configuration of the store itself? :)
Something like that:
// at configureStore.js
import { createStore } from 'redux';
import reducer from '../path/to/your/root/reducer'; // assuming that you use combineReducer function to gather all reducers in one place
export default createStore(reducer);
Then in your app root you need to wrap entry component with the store provider:
import store from './store/configureStore';
import { Provider } from 'react-redux';
export default () => (
<Provider store={store}>
<AppRootComponent />
</Provider>
);
AppRootComponent -> your initial app component
For reference - how to configure store
UPD:
Looks like you were trying to pass the action creator to the dispatch function, instead of invoking it actually. Just make a call of that creator in the dispatch:
login: () => dispatch(actions.login()),
BTW, here is the working example of your case
I am fairly new to React and Redux and I have an issue with my component not updating on the final dispatch that updates a redux store. I am using a thunk to preload some data to drive various pieces of my site. I can see the thunk working and the state updating seemingly correctly but when the data fetch success dispatch happens, the component is not seeing a change in state and subsequently not re rendering. the interesting part is that the first dispatch which sets a loading flag is being seen by the component and it is reacting correctly. Here is my code:
actions
import { programsConstants } from '../constants';
import axios from 'axios'
export const programsActions = {
begin,
success,
error,
};
export const loadPrograms = () => dispatch => {
dispatch(programsActions.begin());
axios
.get('/programs/data')
.then((res) => {
dispatch(programsActions.success(res.data.results));
})
.catch((err) => {
dispatch(programsActions.error(err.message));
});
};
function begin() {
return {type:programsConstants.BEGIN};
}
function success(data) {
return {type:programsConstants.SUCCESS, payload: data};
}
function error(message) {
return {type:programsConstants.ERROR, payload:message};
}
reducers
import {programsConstants} from '../constants';
import React from "react";
const initialState = {
data: [],
loading: false,
error: null
};
export function programs(state = initialState, action) {
switch (action.type) {
case programsConstants.BEGIN:
return fetchPrograms(state);
case programsConstants.SUCCESS:
return populatePrograms(state, action);
case programsConstants.ERROR:
return fetchError(state, action);
case programsConstants.EXPANDED:
return programsExpanded(state, action);
default:
return state
}
}
function fetchPrograms(state = {}) {
return { ...state, data: [], loading: true, error: null };
}
function populatePrograms(state = {}, action) {
return { ...state, data: action.payload, loading: false, error: null };
}
function fetchError(state = {}, action) {
return { ...state, data: [], loading: false, error: action.payload };
}
component
import React from "react";
import { connect } from 'react-redux';
import { Route, Switch, Redirect } from "react-router-dom";
import { Header, Footer, Sidebar } from "../../components";
import dashboardRoutes from "../../routes/dashboard.jsx";
import Loading from "../../components/Loading/Loading";
import {loadPrograms} from "../../actions/programs.actions";
class Dashboard extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.dispatch(loadPrograms());
}
render() {
const { error, loading } = this.props;
if (loading) {
return <div><Loading loading={true} /></div>
}
if (error) {
return <div style={{ color: 'red' }}>ERROR: {error}</div>
}
return (
<div className="wrapper">
<Sidebar {...this.props} routes={dashboardRoutes} />
<div className="main-panel" ref="mainPanel">
<Header {...this.props} />
<Switch>
{dashboardRoutes.map((prop, key) => {
let Component = prop.component;
return (
<Route path={prop.path} component={props => <Component {...props} />} key={key} />
);
})}
</Switch>
<Footer fluid />
</div>
</div>
);
}
}
const mapStateToProps = state => ({
loading: state.programs.loading,
error: state.programs.error
});
export default connect(mapStateToProps)(Dashboard);
The component should receive updated props from the success dispatch and re render with the updated data. Currently the component only re renders on the begin dispatch and shows the loading component correctly but doesn't re render with the data is retrieved and updated to the state by the thunk.
I've researched this for a couple days and the generally accepted cause for the component not getting a state refresh is inadvertent state mutation rather than returning a new state. I don't think I'm mutating the state but perhaps I am.
Any help would much appreciated!
Update 1
As requested here's the code for creating the store and combining the reducers
store:
const loggerMiddleware = createLogger();
const composeEnhancers =
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
}) : compose;
const enhancer = composeEnhancers(
applyMiddleware(
thunk,
loggerMiddleware)
);
export const store = createStore(rootReducer, enhancer);
reducer combine:
import { combineReducers } from 'redux';
import { alert } from './alert.reducer';
import { programs } from './programs.reducer';
import { sidenav } from './sidenav.reducer';
const rootReducer = combineReducers({
programs,
sidenav,
alert
});
export default rootReducer;
The 2nd param is expected to be [preloadedState]:
export const store = createStore(rootReducer, {} , enhancer);
axios.get return a promise that you need to await for to get your data:
Try this:
export const loadPrograms = () => async (dispatch) => {
dispatch(programsActions.begin());
try {
const res = await axios.get('/programs/data');
const data = await res.data;
console.log('data recieved', data)
dispatch(programsActions.success(data.results));
} catch (error) {
dispatch(programsActions.error(error));
}
};
const mapStateToProps = state => ({
loading: state.programs.loading,
error: state.programs.error,
data: state.programs.data,
});
Action Call
import React from 'react';
import { connect } from 'react-redux';
import { loadPrograms } from '../../actions/programs.actions';
class Dashboard extends React.Component {
componentDidMount() {
// Try to call you action this way:
this.props.loadProgramsAction(); // <== Look at this
}
}
const mapStateToProps = state => ({
loading: state.programs.loading,
error: state.programs.error,
});
export default connect(
mapStateToProps,
{
loadProgramsAction: loadPrograms,
},
)(Dashboard);
After three days of research and refactoring, I finally figured out the problem and got it working. Turns out that the version of react-redux is was using (6.0.1) was the issue. Rolled back to 5.1.1 and everything worked flawlessly. Not sure if something is broken in 6.0.1 or if I was just using wrong.
I am learning Redux. I am trying to connect dispatch function from container component to presentation component.
Container Component:
//FILE : app/javascript/packs/containers/registration.js
import { connect } from 'react-redux'
import { fetchCountry } from '../actions'
import Countries from '../components/registration/countries';
const getCountry = (state, filter) => {
switch (filter) {
case 'region':
console.log("Get Country Triggered",state)
default:
console.log("Get Country Default Triggered",state)
}
}
const mapStateToProps = state => ({
countries:getCountry(state,'region')
})
const mapDispatchToProps = dispatch => ({
fetchCountry: region => dispatch(fetchCountry(region))
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(Countries)
Presentational Component
// FILE : /app/javascript/packs/components/registration/countries.jsx
import React from 'react'
import PropTypes from 'prop-types'
const Countries = ({ fetchCountry }) => (
<ul>
<li>
<button onClick={() => fetchCountry('region')}>Get Country</button>
</li>
</ul>
)
Countries.propTypes = {
fetchCountry: PropTypes.func.isRequired
}
Actions :
//FILE : /app/javascript/packs/actions/index.js
/* Action types */
export const FETCH_COUNTRY = "FETCH_COUNTRY";
export const FETCH_CITY = "FETCH_CITY";
/* Action creators */
export function fetchCountry(region) {
return { type: FETCH_COUNTRY, region };
}
Reducer
// FILE: /app/javascript/packs/reducers/fetchPlace.js
const fetchPlace = (state = [], action) => {
switch (action.type) {
case 'FETCH_COUNTRY':
console.log('FETCH COUNTRY Reducer');
default:
return state
}
}
export default fetchPlace
I am fetchCountry is undefined error when I try to load the page.
warning.js:33 Warning: Failed prop type: The propfetchCountryis marked as required inCountries, but its value isundefined.
I understand , i am missing some basics here, any help will be highly appreciated.
Maybe this line is your problem. Depends on if you have an index.js
import { fetchCountry } from '../actions'
should be
import { fetchCountry } from '../actions/action_file_name'
I've just started implementing Redux in a React application, and it's the first time i try to, so please bear with me.
My problem is that i can't access the data in my component this this.props.questions
I have a simple action which is supposed to async fetch some data
export function fetchQuestions(url) {
const request = axios.get('url');
return (dispatch) => {
request.then(({data}) => {
dispatch({ type: 'FETCH_QUESTIONS', payload: data });
console.log(data);
});
};
}
Which is picked up my reducer questions_reducer
export default function(state = [], action) {
switch(action.type) {
case 'FETCH_QUESTIONS':
console.log('Getting here');
return state.concat([action.payload.data]);
console.log('But not here');
}
return state;
}
My index reducer looks like this:
import { combineReducers } from 'redux';
import fetchQuestions from './question_reducer';
const rootReducer = combineReducers({
questions: fetchQuestions
});
export default rootReducer;
I pass it to my store where i apply the thunk middleware and finally into <Provider store={store}> which wraps my app, but the prop just returns undefined in my React component
configureStore:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers';
export default function configureStore(initialState) {
return createStore(
rootReducer,
initialState,
applyMiddleware(thunk)
);
}
I don't know if the console.log is to be trusted but it logs from my questions_reducer before the data is returned from the dispatch in my action
EDIT (Component)
class QuestionsRoute extends Component {
constructor(props) {
super(props);
this.state = {
};
}
componentDidMount() {
this.props.fetch('someUrl);
setTimeout(function(){ console.log(this.props.questions) },
1500);
}
render() {
{console.log(this.props.questions)}
return (
<div>
<1>Hello</1>
{this.props.questions !== undefined ?
<p>We like props</p>: <p>or not</p>
}
</div>
);
}
};
const mapStateToProps = (state) => {
return {
questions: state.questions,
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetch: () => dispatch(fetchQuestions())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(QuestionsRoute);
In your reducer
export default function(state = [], action) {
switch(action.type) {
case 'FETCH_QUESTIONS':
return state.concat([action.payload.data]);
}
return state;
}
You should probably instead have return state.concat([action.payload]);
Since from dispatch({ type: 'FETCH_QUESTIONS', payload: data }); we see that payload is data, it doesn't contain it.
Update: I'd recommend setting up redux-devtools / redux-devtools-extension / react-native-debugger so you can visually see your actions and store state live - makes things like this a lot easier to debug!