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.
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 am new to redux and react both. I am trying out my first redux app where my state is getting passed as undefined. Although I searched around the internet to figure the issue but couldn't find out whats the problem.
Below are the code snippets -
Init.js
export default function() {
return {
isLoggedIn: false,
username: "admin"
};
}
Reducer.js
import initialState from "./init";
let currentState = initialState;
export default function(state = currentState, action) {
if (action.type === "ONLICK") {
return state;
} else {
return state;
}
}
Index.js
const allReducers = combineReducers({
currentstate: currentState
});
export default allReducers;
currentState.js
import React, { Component } from "react";
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import { allreducers } from "./../reducer/index";
class CurrentState extends Component {
render() {
console.log("Logged In-" + this.props.isLoggedIn);
return <div>{this.props.isLoggedIn}</div>;
}
}
function mapStateToProps(state) {
return {
isLoggedIn: state.isLoggedIn
};
}
export default connect(mapStateToProps)(CurrentState);
navbar.js
import React, { Component } from "react";
import CurrentState from "./../container/currentstate";
import "bootstrap/dist/css/bootstrap.css";
class Navbar extends Component {
constructor() {
super();
}
render() {
return (
<div>
<h3>State -</h3>
<CurrentState />
</div>
);
}
}
Navbar.js is where I am displaying the one of the attributes of the state. But in the console when I am trying to print the value it is getting displayed as undefined.
You're getting state in wrong way in mapStateToProps
Use
function mapStateToProps(state) {
return {
isLoggedIn: state.currentstate
};
}
instead
function mapStateToProps(state) {
return {
isLoggedIn: state.isLoggedIn
};
}
Hope so it will help
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()
I have this autocomplete component that takes an array of terms as a dataSource prop. The data I want to feed in resides in a public API, and I've followed the tutorial here to get to the code below. But this tutorial (and many others out there) explain how to bind these actions to an event, whereas I want to populate this prop with data on page load. How would I go about doing that?
actions.js
import fetch from 'isomorphic-fetch';
export function loadSchools(termId) {
return {
type: 'LOAD_SCHOOLS',
termId
};
}
export function receiveSchools(termId, json) {
return {
type: 'RECEIVE_SCHOOLS',
termId,
schools: json.data.children.map(child => child.data), // ???
receivedAt: Date.now()
};
}
export function getSchools(termId) {
return function (dispatch) {
dispatch(loadSchools(termId));
return fetch('http://www.northwestern.edu/class-descriptions/4650/index-v2.json')
.then(response => {
if (response.status >= 400) {
throw new Error('Bad response from server');
}
return response.json();
})
.then(data => dispatch(receiveSchools(termId, data)));
};
}
reducers.js
const initialState = {
schoolsData: {
isFetching: false,
lastUpdated: 0,
schools: []
}
};
function schools(state = initialState, action) {
switch (action.type) {
case 'LOAD_SCHOOLS':
return {
...state,
isFetching: true
};
case 'RECEIVE_SCHOOLS':
return {
...state,
isFetching: false,
schools: action.schools,
lastUpdated: receivedAt
}
default:
return state;
}
}
export default schools;
Search.jsx
import React from 'react';
import AutoComplete from 'material-ui/AutoComplete';
export default class Search extends React.Component {
render() {
return (
<AutoComplete
hintText="Search for something."
dataSource={this.props.searchdata}
maxSearchResults={15}
filter={AutoComplete.caseInsensitiveFilter}
onNewRequest={}
/>
);
}
}
Search.propTypes = {
searchdata: React.PropTypes.array.isRequired,
onSelect: React.PropTypes.func
};
index.jsx
import 'babel-polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import { grey500, white, fullBlack } from 'material-ui/styles/colors';
import { fade } from 'material-ui/utils/colorManipulator';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import schools from './reducers/reducers';
import colors from './colors';
import NavBar from './components/NavBar.jsx';
import Serif from './components/Serif.jsx';
const store = createStore(schools, applyMiddleware(thunkMiddleware));
const muiTheme = getMuiTheme({
palette: {
primary1Color: colors.northwesternPurple,
primary2Color: colors.northwesternPurple120,
primary3Color: grey500,
accent1Color: colors.northwesternPurple30,
accent2Color: colors.richBlack10,
accent3Color: colors.richBlack50,
textColor: colors.richBlack80,
alternateTextColor: white,
canvasColor: white,
borderColor: colors.richBlack20,
disabledColor: fade(colors.richBlack80, 0.3),
pickerHeaderColor: colors.northwesternPurple,
clockCircleColor: fade(colors.richBlack80, 0.07),
shadowColor: fullBlack
}
});
class App extends React.Component {
render() {
return (
<Provider store={store}>
<MuiThemeProvider muiTheme={muiTheme}>
<div> {/* MuiThemeProvider requires stricly one child element */}
<NavBar />
<Serif /> {/* This component contains SearchContainer, which in turn contains Search */}
</div>
</MuiThemeProvider>
</Provider>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
You can render your Search component from another component, let's call it SearchContainer. SearchContainer is decorated by the connect function from react-redux that has as only role to dispatch the action to fetch the schools. SearchContainer doesn't render Search component until the school are fetched.
Here an example of what the code would look like. Here I assume you don't use react-redux.
First you have a small problem in your initial state in reducers.js. It should be:
const initialState = {
isFetching: false,
lastUpdated: 0,
schools: []
};
function schools(state = initialState, action) {
switch (action.type) {
case 'LOAD_SCHOOLS':
return {
...state,
isFetching: true
};
case 'RECEIVE_SCHOOLS':
return {
...state,
isFetching: false,
schools: action.schools,
lastUpdated: receivedAt
}
default:
return state;
}
}
SearchContainer.js
// ./containers/SearchContainer.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { loadSchools } from '../actions/actions'
import Search from '../components/Search';
class SearchContainer extends Component {
componentDidMount() {
this.props.loadSchools(this.props.termId);
},
render() {
const {
schools,
isFetching
} = this.props;
if (isFetching) {
return null;
}
return <Search schools={schools} />;
}
}
const mapStateToProps = (state) => ({
isFetching: state.isFetching,
schools: state.schools
});
const mapActionsToProps = (dispatch) => ({
loadSchools: (termId) => dispatch(loadSchools(termId)),
});
export default connect(mapStateToProps, mapActionsToProps)(SearchContainer);
In this way, at the first render, your Search component is not rendered. It is rendered, only after the schools are loaded.
You can dispatch the LOAD_SCHOOLS action from the componentDidMount lifecycle method (maybe in your Serif component but I can't see the code for that).
From the docs:
componentDidMount() is invoked immediately after a component is mounted. Initialization that requires DOM nodes should go here. If you need to load data from a remote endpoint, this is a good place to instantiate the network request. Setting state in this method will trigger a re-rendering.
https://facebook.github.io/react/docs/react-component.html#componentdidmount
I'm new to redux and having trouble wrapping my head around presentational and container components.
Relevant stack:
react v0.14.8
react-native v0.24.1
redux v3.5.2
react-redux v4.4.5
The issue:
I have a login button component, which when rendered checks the login status and calls the onSuccessfulLogin action which updates the state with the user's Facebook credentials.
However, when trying to separate this into separate presentational/container components, I'm unable to call the onSuccessfulLogin action: Error: onSuccessfulLogin is not defined.
What am I doing wrong here? I'd imagine there's something simple that I'm not understanding with the relationship between the two components and the connect() function.
Presentational Component (Login.js)
import React, { PropTypes } from "react-native";
import FBLogin from "react-native-facebook-login";
import UserActions from "../users/UserActions";
class LoginPage extends React.Component {
render() {
const { userData, onSuccessfulLogin } = this.props;
return (
<FBLogin
permissions={["email","user_friends"]}
onLoginFound= { data => {
onSuccessfulLogin(data.credentials);
}}
/>
)
}
};
export default LoginPage;
Container Component (LoginContainer.js)
import { connect } from 'react-redux';
import LoginPage from "../login/LoginPage";
import UserActions from "../users/UserActions";
const mapDispatchToProps = (dispatch) => {
return {
onSuccessfulLogin: (userData) => {
dispatch(UserActions.userLoggedIn(userData))
}
}
}
const mapStateToProps = (state) => {
return {
userData: state.userData
}
}
const LoginContainer = connect(
mapStateToProps,
mapDispatchToProps
)(LoginPage);
export default LoginContainer;
Also, if I wanted to make the updated state.userData accessible to the LoginPage component, how would I do that? Any help is appreciated!
Solved! When using ES6 classes, you're required to call super(props) in a constructor method in order to access the container's properties in the connected presentational component:
class LoginPage extends React.Component {
constructor(props){
super(props);
}
render(){
// ...
}
}
Your container component is supposed to be a component and it must have a render function with the dumb/presentational components you want to render.
import { connect } from 'react-redux';
import LoginPage from "../login/LoginPage";
import UserActions from "../users/UserActions";
class LoginContainer extends React.Component {
constructor(props){
super(props);
}
render() {
return (
<LoginPage userData={this.props.userData}
onSuccessfulLogin={this.props.onSuccessfulLogin}
/>
)
}
};
const mapDispatchToProps = (dispatch) => {
return {
onSuccessfulLogin: (userData) => {
dispatch(UserActions.userLoggedIn(userData))
}
}
}
const mapStateToProps = (state) => {
return {
userData: state.userData
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(LoginPage);