I am using 'react-fileupload' to upload files on my server. In case of success I receive response with content of this file. So in one component I want to upload file and change stores state and in another component I want to show that data.
But i don't know why my dispatch function doesn't work.
Component with uploader:
import React, { Component } from 'react';
import FileUpload from 'react-fileupload';
import { connect } from 'react-redux';
import { updateOverview } from '../actions/index';
import { bindActionCreators } from 'redux';
class Header extends Component {
render() {
const options = {
baseUrl: 'http://127.0.0.1:8000/api/upload_file',
chooseAndUpload: true,
uploadSuccess: function(res) {
console.log('success');
updateOverview(res.data);
},
uploadError: function(err) {
alert(err.message);
}
};
return (
<div>
<FileUpload options={options} ref="fileUpload">
<button
className="yellow darken-2 white-text btn-flat"
ref="chooseAndUpload">
Upload
</button>
</FileUpload>
</div>
);
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ updateOverview }, dispatch);
}
export default connect(null, mapDispatchToProps)(Header);
Component where data is shown:
import React, { Component } from 'react';
import { connect } from 'react-redux';
class Overview extends Component {
renderContent() {
console.log(this.props.overview);
if (!this.props.overview) {
return <div> Upload file!</div>;
}
return this.props.overview;
}
render() {
return (
<div>
<h1>Overview</h1>
{this.renderContent()}
</div>
);
}
}
function mapStateToProps({ overview }) {
return { overview };
}
export default connect(mapStateToProps)(Overview);
Action creator:
import { FETCH_OVERVIEW } from './types';
export function updateOverview(data) {
return { type: FETCH_OVERVIEW, payload: data };
}
reducer index.js
import { combineReducers } from 'redux';
import overviewReducer from './overviewReducer';
export default combineReducers({
overview: overviewReducer
});
overviewReducer.js
import { FETCH_OVERVIEW } from '../actions/types';
export default function(state = null, action) {
switch (action.type) {
case FETCH_OVERVIEW:
return action.payload;
default:
return state;
}
}
The only use case for bindActionCreators is when you want to pass some action creators down to a component that isn't aware of Redux, and you don't want to pass dispatch or the Redux store to it.
Your Header component already knows how to create action.
Considering the your Home component need ,your don't need of bindActionCreators.
The correct way to do this.
const mapDispatchToProps = dispatch => {
return {
callUpdateOverview: () => {
dispatch({ updateOverview });
}
}
}
And in the Header render method :
this.props.updateOverview(res.data);
EDIT :
In your Home Component render method,
const homeThis = this; //save `this` object to some variables
^^^^^^^^^^^^^^^^^^^^^^
const options = {
baseUrl: ..,
chooseAndUpload: ..,
uploadSuccess: function (res) {
homeThis.props.callUpdateOverview();// call using `homeThis`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}
};
In your code, you are calling
updateOverview(res.data);
actually which should be called like,
this.props.updateOverview(res.data);
Because, the redux will listen only to the dispatch bound actions, so to enable that, we use connect function from react-redux package, so that redux will know to update itself upon the action execution.
connect will bind your action to the component props on this.props, so it is very essential to use this.props.action() and not just action()
Related
I encountered this problem when I was testing my newly created action and reducer. The prop is not being updated even though I'm setting it to a fixed value within my reducer.
Component:
class <ComponentName> extends Component {
componentDidMount() {
login()
}
render() {
if(this.props.isLogged)
return (
<App/>
);
else
return (
<ErrorScreen/>
);
}
}
function mapStateToProps(state) {
return {
isLogged:state.auth.isLogged
}
}
const mapDispatchToProps = (dispatch) => {
return {
login: () => dispatch(login())
};
};
export default connect(mapStateToProps,mapDispatchToProps)(<ComponentName>)
Action:
export function login() {
return {
type:"TEST"
}
}
Reducer:
const initState = {
isLogged: false,
}
export default (state=initState, action) => {
switch(action.type) {
case "TEST":
return {
...state,
isLogged: true
}
break;
default:
return state
}
}
Combine Reducer:
import {combineReducers} from 'redux'
import AuthenticationReducer from './authenticationReducer'
export default combineReducers({
auth: AuthenticationReducer
})
Provider:
import React, {Component} from "react";
import <ComponentName> from './app/screens/<ComponentName>'
import store from './app/store'
import {Provider} from 'react-redux'
export default () =>
<Provider store={store}>
<<ComponentName>/>
</Provider>;
Been trying to debug this for some time now. I still don't know why this is happening. Maybe I implemented it wrongly? If there are some files I forgot to include, please inform me. Thanks and have a nice day!
The reason your code isn't working as expected is because you're calling the login() action creator, rather than the login() method that is returned from mapDispatchToProps() (and injected into the props of <ComponentName/>).
Try revising your code by adding this.props before your call to login() like so:
class <ComponentName> extends Component {
componentDidMount() {
// Update this line here so that the login() method
// injected by connect() is called (ie via this.props)
this.props.login()
}
render() {
if(this.props.isLogged)
return <App/>
else
return <ErrorScreen/>
}
}
I have been building a React and Redux app. I created a simple container and an action creator which carries an action with a simple console.log() one of the users I have written down in the state (reducer).
The problem is, when I click on the rendered state, it says 'user' is not defined. Why? What can I do to let the function recognize the state?
Container
import React, { Component } from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {selectUser} from '../actions/index';
class UserList extends Component {
renderUser() {
return <div onClick={this.props.selectUser(user)}>
{this.props.users[0].giovanni}
</div>
}
render() {
return (
<div>{this.renderUser()}</div>
);
}
}
function mapStateToProps(state) {
return {
users: state.users
}
}
function matchDispatchToProps(dispatch) {
return bindActionCreators({selectUser: selectUser}, dispatch);
}
export default connect(mapStateToProps, matchDispatchToProps)(UserList);
Action
export const selectUser = (user) => {
console.log('yo how are you?', user.users[0]);
return {
type: 'USER_SELECTED',
payload: user
}
}
Reducer
export default function () {
return [
{
giovanni: 'operaio',
marco: 'vigile',
luca: 'esattore',
}
]
}
Index Reducer
import {combineReducers} from 'redux';
import reducerMain from './reducer_main';
const allReducers = combineReducers({
users: reducerMain
});
export default allReducers;
This looks incorrect for a number of reasons
renderUser() {
return (
<div onClick={this.props.selectUser(user)}>
{this.props.users[0].giovanni}
</div>
);
}
onClick receives a function. In all likelihood, you meant the following
<div onClick={() => this.props.selectUser(user)}>
And not the call to this.props.selectUser by itself.
What is user? It is not defined. I guess you meant
renderUser() {
return (
<div onClick={() => this.props.selectUser(this.props.users[0])}>
{this.props.users[0].giovanni}
</div>
);
}
The action must look like this, since the user object is already passed
export const selectUser = (user) => {
console.log('yo how are you?', user);
return {
type: 'USER_SELECTED',
payload: user
}
}
You event handler will trigger asynchronous update of the state, so you will most likely not have the value on the same rendering of the component, you can use a default value if it is not set for example: this.props.users[0].giovanni || ''} in case there is not value or a conditional rendering.
For example: i have 2 controll-view container user.cv.jsx and sidebar.cv.jsx
Screen consist of User and Sidebar. Sidebar rendering in User screen.
User container:
import React from 'react'
import {Link} from 'react-router-dom';
import { connect } from 'react-redux';
import UserTypeComponents from '../components/user_type.jsx'
import Sidebar from '../../sidebar/containers/sidebar.cv.js'
import * as showList from '../action/list.action.js';
import * as userLimit from '../action/limit.action.js';
import PropTypes from 'prop-types'
function mapStateToProps (state) {
return {...state}
}
class UserType extends React.Component {
constructor (props, context) {
super(props);
this.context = context;
if(!this.props.oauth.isAuthenticating) {
this.context.router.history.push('/login');
return;
}
}
componentDidMount() {
}
render() {
console.log(this.props);
return (<div>
<Sidebar />
<UserTypeComponents {...this.props} />
</div>);
}
}
UserType.contextTypes = {
router: PropTypes.object
}
export default connect(mapStateToProps)(UserType);
And Sidebar Container:
import React from 'react'
import {Link} from 'react-router-dom';
import ShowSidebar from '../components/sidebar.jsx';
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import Preloader from '../../../helpers/preloader.helper.js'
import * as active from '../action/active.action.js'
import * as list from '../action/list.action.js'
import * as show from '../action/show.action.js'
import {DEFAULT_COMPONENTS} from '../constant/sidebar.const.js';
function mapStateToProps (state) {
return state.sidebar
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
...active,
...list,
...show
}, dispatch);
}
class Sidebar extends React.Component {
constructor (props) {
super(props);
}
listOfLinks(){
const makeRequest = async () => {
try {
const data = await (await fetch('http://localhost:3000/sidebar')).json(),
active = this.activeComponent(data);
this.props.list(data);
this.props.active(active);
} catch (err) {
console.log(err)
}
}
makeRequest()
}
activeComponent(data){
for(let key of data){
if(location.pathname.indexOf(key.name.toLowerCase()) != -1){
return key.name.toLowerCase();
}
}
return DEFAULT_COMPONENTS;
}
componentWillMount() {
this.listOfLinks();
}
activeSidebarState(event){
let parent = event.target.parentNode,
target = _$('.site-sidebar__name', parent),
text = target.innerText.toLowerCase();
this.props.active(text);
}
render() {
const loading = this.props.sidebar.links.length;
return (loading ? <ShowSidebar changeActive={::this.activeSidebarState} active={this.props.sidebar.active} links={this.props.sidebar.links} /> : <Preloader />);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Sidebar);
For all this, action and redusers are written. The sidebar sends a request to the server and requests all the modules and forms links to them, too. The user module is accessing the server and requires all users. The problem is that the preloader is being formed in the sidebar, and when the sidebar is loaded the preloader disappears. But the users still could not boot.
The question is: How to control the loading of the sidebar and the user, so that when these two components are updated, the state remove the preloader.
A common practice is to store isFetching flag in the reducer and update it in respond to fetch actions. For example:
function users(state = { users: [], isFetching: false }, action) {
switch (action.type) {
case 'FETCH_USERS_START':
return { ...state, isFetching: true };
case 'FETCH_USER_SUCCESS':
return { ...state, isFetching: false, users: action.payload.users };
default:
return state;
}
}
Then you can access it from both your components via mapStateToProps and show the preloader.
A main thing here is that you need to move the async call to an action, so reducer will be able to react to it. You can use redux-thunk middleware.
I'm new to react and I'm trying to understand on to make async ajax request. I was able to get the request completed and the data returned added to my state, but I can't render the data to my component. Here's my setup.
UserPage component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { getUsers } from '../../actions';
class User extends Component {
displayName: 'User';
componentWillMount() {
this.props.getUsers();
}
renderUsers() {
return this.props.users.map(user => {
return (
<h5>{user.Name}</h5>
);
});
}
render() {
var component;
if (this.props.users) {
component = this.renderUsers()
} else {
component = <h3>asdf</h3>;
}
return (
<div>
{component}
</div>
);
};
};
function mapStateToProps(state) {
return {
users: state.all
};
}
export default connect(mapStateToProps, { getUsers })(User);
Action
import request from '../helpers/request';
import { GET_USERS } from './types';
export function getUsers() {
return request.get(GET_USERS, 'Person/GetPeople');
}
With the get function from request.js module
get: function (action, url) {
return (dispatch) => {
axios.get(`${ROOT_URL}${url}`)
.then(({ data }) => {
dispatch({
type: action,
payload: data
});
});
};
}
User_reducer
import { GET_USERS } from '../actions/types';
export default function (state = {}, action) {
switch (action.type) {
case GET_USERS:
console.log(action);
return { ...state, all: action.payload }
default:
return state;
};
}
When I load the UserPage component, I can see the request being done and the state being updated in the redux dev tool, but I can't display this.props.users.
If I take out the if(this.props.users) in the render() method of the User component, I get an undefined on this.props.users.
What am I doing wrong?
Any help greatly appreciated! Thank you
SOLVED
The solution was to set the users property to state.users.all in the mapStateToProps function.
function mapStateToProps(state) {
return {
users: state.users.all
};
}
Thank you
Could you check, do you have this.renderUsers() defined in that render function? It might be you forgot to bind it, and that function is undefined?
Try to add this.renderUsers = this.renderUsers.bind(this); in your constructor and see, if it helps.
I've got a simple component that calls an action when a user loads a page, and inside that action, I'm trying to dispatch another action to set the loggedIn state of the store to true or false:
import React, { Component } from 'react'
import { Link, browserHistory } from 'react-router'
import $ from 'jquery'
class Login extends Component {
constructor(props) {
super(props)
}
componentDidMount() {
this.props.actions.guestLoginRequest()
}
render() {
return (
<div>
<div classNameName="container">
<div className="row">
We are signing you in as a guest
</div>
</div>
</div>
)
}
}
export default Login
I can get the login information when the guestLoginRequest action is called, but when I try to dispatch another action inside of it, nothing happens:
guestLoginRequest: function(){
var ref = new Firebase("https://penguinradio.firebaseio.com");
ref.authAnonymously(function(error, authData) {
if (error) {
console.log("Login Failed!", error);
} else {
console.log("Authenticated successfully with payload:", authData);
return dispatch => {
dispatch(actions.setLoginStatus(true, authData))
console.log("dispatched");
};
}
});
}
I get an error of Uncaught ReferenceError: dispatch is not defined when I remove the return dispatch => { } statement. In my store I am using redux-thunk, so I can dispatch inside of actions:
// Store.js
import { applyMiddleware, compose, createStore } from 'redux'
import rootReducer from './reducers'
import logger from 'redux-logger'
import thunk from 'redux-thunk'
let finalCreateStore = compose(
applyMiddleware(thunk, logger())
)(createStore)
export default function configureStore(initialState = { loggedIn: false }) {
return finalCreateStore(rootReducer, initialState)
}
I am mapping the dispatch to props in my app.js as well:
function mapStateToProps(state) {
return state
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
Just in case it could be helpful, here is my client.js and reducer files:
// client.js
import React from 'react'
import { render } from 'react-dom'
import App from '../components/App'
import configureStore from '../redux/store'
import { Provider } from 'react-redux'
let initialState = {
loggedIn: false
}
let store = configureStore(initialState)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app')
)
// Reducer.js
import { combineReducers } from 'redux'
let LoginStatusReducer = function reducer(loggedIn = false, action) {
switch (action.type) {
case 'UPDATE_LOGIN_STATUS':
return loggedIn = action.boolean
default:
return loggedIn
}
}
export default LoginStatusReducer
const rootReducer = combineReducers({
loggedIn: LoginStatusReducer
})
export default rootReducer
Any ideas why my dispatch function isn't working? I'm confused since I did set up redux-thunk with my store, and I'm using code similar to the docs when I call return dispatch => { }. Is there something I'm missing? Thank you in advance for any advice!
You need your action to return a function to utilize the thunk middleware, then redux will inject the dispatcher into it. You mixed your dispatcher invocation with the implementation detail. The following snippet fixes both defects.
guestLoginRequest: function(){
return function (dispatch) {
var ref = new Firebase("https://penguinradio.firebaseio.com");
ref.authAnonymously(function(error, authData) {
if (error) {
console.log("Login Failed!", error);
} else {
console.log("Authenticated successfully with payload:", authData);
dispatch(actions.setLoginStatus(true, authData))
console.log("dispatched");
}
});
}
}
In addition, you need to dispatch your action correctly on the Login class.
dispatch(this.props.actions.guestLoginRequest())
Your action invocation is always done by invoking dispatch. The flow should be something like this:
React component --> dispatch ---> API call (thunk middleware) --> dispatch ---> reducer
Make sure useDispatch imported
import { useDispatch } from "react-redux";
First, you need to import
import { useDispatch } from "react-redux";
then call it.
const dispatch = useDispatch();
now you are ready for use.