Use React-Redux To Develop a Login Page - reactjs

I try to use React-Redux to develope a Login Page and I encounter with some problems.
I want to send the TextFields about account and password to server by AJAX, but when I click the login button, this login button can not get the value of TextFields.
I do not want to use "form tag" and I try to do my best to solve this problem by myself, but I still suspect my solution, so I want to find some advise about this problem.
My solution is the following
TextFields will trigger an action which will set Login info into the next state.
The Login Button will do send these info to server only.
The following is the part of codes.
TextField:
class BaseInputComponent extends React.Component {
constructor(props) {
super(props);
this._handleChange = this._handleChange.bind(this);
}
_handleChange(e) {
this.props.onChange({infoType: this.props.dataType, data: e.target.value})
}
render() {
var style = {
marginBottom: 30
};
return (
<div style={style}>
<TextField
hintText={ this.props.hintText }
floatingLabelText={ this.props.floatingLabelText }
type={ this.props.type }
onChange={ this._handleChange }
value={this.props.textField}
/>
</div>
);
}
}
A Login Page
class UserPaperComponent extends React.Component {
constructor(props) {
super(props);
}
handleClickBtn() {
this.props.actions.userLogin()
}
render() {
var style = {
padding: 20,
textAlign: 'center',
};
return (
<Paper style={style} zDepth={5}>
<UserPaperTitleComponent text={this.props.userPaperText}/>
<Divider />
<BaseInputComponent dataType="userAccount" textField={this.props.userInfo.userAccount} onChange={ this.props.actions.setUserInfo } floatingLabelText="Account"/>
<BaseInputComponent dataType="userPwd" textField={this.props.userInfo.userPwd} onChange={ this.props.actions.setUserInfo } floatingLabelText="Password" type="password"/>
<Divider />
<br />
<RaisedButton label={this.props.userPaperText} onMouseUp={ this.handleClickBtn } />
</Paper>
);
}
}
In the reduce
const initialState = {
userAccount: '',
userPwd: '',
userHadLogin: false
};
module.exports = function(state = initialState, action) {
/* Keep the reducer clean - do not mutate the original state. */
//let nextState = Object.assign({}, state);
switch(action.type) {
case 'SET_USER_INFO': {
console.log(action.paras)
let nextState = Object.assign({}, state);
nextState[action.paras.infoType] = action.paras.data;
return nextState;
} break;
case 'USER_LOGIN': {
// Modify next state depending on the action and return it
let nextState = Object.assign({}, state);
nextState.userHadLogin = true;
return nextState;
} break;
case 'USER_LOGOUT': {
// Modify next state depending on the action and return it
let nextState = Object.assign({}, initialState);
return nextState;
} break;
default: {
/* Return original state if no actions were consumed. */
return state;
}
}
}

try binding binding the handler with "this"
<RaisedButton label={this.props.userPaperText} onMouseUp={ this.handleClickBtn.bind(this) } />

Related

How to pass componentDidMount function to deep layer children in React

I have question about passing componentDidMount function from parent to deep laying child.
I have a list of items, which are selected by items status. After I change one of the item's status, I need to re-render parent to get new data. Tricky part for me is that, that I can't find way, how to pass componentDidMount function or action to fetch my list's data again.
My parent class:
class Page extends React.Component {
componentDidMount() {
this.props.onCompMount();
}
render() {
const { error, loading, list } = this.props;
const pageListProps = {
loading,
error,
list,
};
return (
<article>
<div>
<PageList {...pageListProps} />
</div>
</article>
);
}
}
My 1st child:
function PageList({ loading, error, list }) {
if (loading) {
return <List component={LoadingIndicator} />;
}
if (error !== false) {
const ErrorComponent = () => (
<ListItem item="Something went wrong, please try again!" />
);
return <List component={ErrorComponent} />;
}
if (list !== false) {
return <List items={list} component={PageItem} />;
}
return null;
}
2nd child:
export class PageItem extends React.PureComponent {
constructor() {
super();
this.state = {
modalIsOpen: false,
};
this.openModal = this.openModal.bind(this);
this.closeModal = this.closeModal.bind(this);
}
openModal() {
this.setState({ modalIsOpen: true });
}
closeModal() {
this.setState({ modalIsOpen: false });
}
render() {
const { item } = this.props;
// Put together the content of the repository
const content = (
<Wrapper>
<h3>{item.title}</h3>
<button onClick={this.openModal}>Decline</button>
<Modal
isOpen={this.state.modalIsOpen}
onRequestClose={this.closeModal}
style={customStyles}
contentLabel="Preview"
>
<Form close={this.closeModal} />
</Modal>
</Wrapper>
);
And my last child where I want after submit to re-render parent container:
export class Form extends React.Component {
render() {
return (
<article>
<form
onSubmit={e => {
e.preventDefault();
this.props.submit();
this.props.close();
//Somehow re-render parent
}}
>
<div className="row" style={{ textAlign: 'start' }}>
Do you really want to change status?
<div className="col-md-12 buttonContainer">
<ButtonA
label="Submit"
style={{ width: '50%' }}
primary
type="submit"
/>
</div>
</div>
</form>
</article>
);
}
}
What I have tried is to reload page with window.location.reload(); and it works. But I think it is bad practice with React. Maybe someone could advise me how to make it better?
EDIT: I am adding parent reducer and 4th child reducer.
Parent reducer:
const initialState = fromJS({
loading: false,
error: false,
listData: {
list: false,
},
});
function pageReducer(state = initialState, action) {
switch (action.type) {
case FETCH_LIST_BEGIN:
return state
.set('loading', true)
.set('error', false)
.setIn(['listData', 'list'], false);
case FETCH_LIST_SUCCESS:
return state
.setIn(['listData', 'list'], action.list)
.set('loading', false);
case FETCH_LIST_FAILURE:
return state.set('error', action.error).set('loading', false);
default:
return state;
}
}
export default pageReducer;
4th child reducer:
const initialState = fromJS({});
function formReducer(state = initialState, action) {
switch (action.type) {
case SUBMIT:
return state;
default:
return state;
}
}
export default formReducer;
We use Redux or React's new Context API to avoid prop drilling issue in react.
In your use you can dispatch action from parent component and connect relevant reducer to your 4th level child. So when your reducer updates global state (store), your connected component will re-render and take updated state as in props.
Just pass this.props.onCompMount() as props to child components and then call it in child component every time it gets updated.

ReactRedux array prop empty

I'm quite new to React and have searched through so many StackOverflow responses but with no joy. So when an array of employees is passed to a reducer and then a component, the prop is empty the first time and then contains data on subsequent renders. Does anyone know how to prevent react rendering until the employees props contains data?
--------Update
So i added isloading to the reducer initial state and tried to send it to the component but i receive a really horrible error
TypeError: In this environment the sources for assign MUST be an object. This error is a performance optimization and not spec compliant
this is my updated case statement
case EMPLOYEES_FETCH_SUCCESS:
return {
list: action.payload,
isloading: false
}
New error message
Many thanks
the data array looks like this when it is populated
data structure
Reducer code:
import { EMPLOYEES_FETCH_SUCCESS, USER_ADD } from "../actions/types";
const INITIAL_STATE = {};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case EMPLOYEES_FETCH_SUCCESS:
return action.payload;
case USER_ADD:
return state;
default:
return state;
}
};
this is my action dispatch statement
dispatch({ type: EMPLOYEES_FETCH_SUCCESS, payload: returnArray });
Component code:
componentWillMount() {
this.props.employeesFetch();
this.createDataSource(this.props);
}
componentWillReceiveProps(nextProps) {
this.createDataSource(nextProps);
}
createDataSource({ employees }) {
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
});
this.dataSource = ds.cloneWithRows(employees);
}
onButtonPress() {
Actions.GroupChatContainer(); //Need to send bar's chat record
}
renderRow(employee) {
return <ListItem employee={employee} />;
}
render() {
console.log(this.props.employees);
return (
<View>
<ListView
style={styles.listStyle}
enableEmptySections
dataSource={this.dataSource}
renderRow={this.renderRow}
/>
<Button onPress={this.onButtonPress.bind(this)}>Group chat</Button>
</View>
);
}
}
const mapStateToProps = state => {
console.log(state.employees);
const employees = _.map(state.employees, (val, uid) => {
return { ...val, uid };
});
return { employees };
};
Does anyone know how to prevent react rendering until the employees
props contain data?
Sure, just do a check and return null:
render() {
if (!this.props.employees) {
return null;
}
return (
<View>
<ListView
style={styles.listStyle}
enableEmptySections
dataSource={this.dataSource}
renderRow={this.renderRow}
/>
<Button onPress={this.onButtonPress.bind(this)}>Group chat</Button>
</View>
);
}
Or inside JSX:
render() {
return (
<View>
{this.props.employees &&
<ListView
style={styles.listStyle}
enableEmptySections
dataSource={this.dataSource}
renderRow={this.renderRow}
/>
}
<Button onPress={this.onButtonPress.bind(this)}>Group chat</Button>
</View>
);
}
You can have two properties for your store. One for the list of employees and another for the loading state.
import { EMPLOYEES_FETCH_SUCCESS, USER_ADD } from "../actions/types";
const INITIAL_STATE = {
list: [],
isLoading: true,
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case EMPLOYEES_FETCH_SUCCESS:
return {
list: action.payload,
isLoading: false,
}
default:
return state;
}
};
And in the render if the state is loading return loading view
render() {
if (!this.props.employees.isLoading) {
return <div>Loading...</div>;
}
...
...
}

Change style onPress + Redux

I want to change the style of the entire app when a button is pressed. I thought I can do this with a reducer. So I created:
ReducerStyles:
const initialState =
{
name: styleNormal,
path: './styles/styleNormal'
}
export default function reducer01 (state = initialState, action) {
switch (action.type) {
case "changeStyleNormal":
return [
...state,
{
name: name: action.payload,
path: './styles/styleNormal'
}
];
case "changeStyleNew":
return [
...state,
{
name: name: action.payload,
path: './styles/styleNew'
}
];
default:
return state
}
}
And Actions:
const CHANGE_STYLE_NORMAL = 'changeStyleNormal';
const CHANGE_STYLE_NEW = 'changeStyleNew';
export function changeStyleNormal(style){
return {
type: CHANGE_STYLE_NORMAL,
payload: style
}
}
export function changeStyleNew(style){
return {
type: CHANGE_STYLE_NEW,
payload: style
}
}
I created 2 styles in the styles folder so only 1 can be applied depending on the one selected/returned from the reducer. By default I have the styleNormal in the Reducer initialState. Imported the Actions, Reducer is combined and mapStateToProps:
function mapStateToProps(state) {
return {
style: state.style
}
}
function mapDispatchToProps(dispatch) {
return {
changeStyleNormal: (style) => {
dispatch(changeStyleNormal(style));
},
changeStyleNew: (style) => {
dispatch(changeStyleNew(style));
}
}
}
Added 2 buttons:
<TouchableOpacity
style={styles.clickMe}
onPress={()=>this.props.changeStyleNew('styleNew')}>
<Text style={styles.black18}>New Style</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.clickMe}
onPress={()=>this.props.changeStyleNormal('styleNormal')}>
<Text style={styles.black18}>Normal Style</Text>
</TouchableOpacity>
Now when the component is called,
render() {
console.log("style: ",this.props.style);
This gives the style as:
I cannot access this.props.style out of the render() so where would I set the var style = this.props.style.path ?
Also, when I click any button, the actions are fine, but the styles are getting appended to the reducer:
I want only the one passed to be in the reducer. So I can use it to set the style.
Is this the proper way to do it? Please help.
Many thanks.
UPDATE 1:
class Ext2 extends Component {
//console.log('Style:', this.props.people); // <= This throws an internal server error 500
// const styles = this.props.style.path; // same error as above
render() {
console.log("style: ",this.props.style); //<= Works
console.log("stylePath: ",this.props.style.path) //<= Works
I cannot access this.props.style out of the render()
what makes you think you can't access it? you can access this.props from anywhere in the class.
Also, when I click any button, the actions are fine, but the styles
are getting appended to the reducer
Your initial state is an object yet you are returning an array from your reducers:
case "changeStyleNew":
return [
...state,
{
name: name: action.payload,
path: './styles/styleNew'
}
];
Instead try returning an object like this:
case "changeStyleNew":
return{
...state,
name: name: action.payload,
path: './styles/styleNew'
}
EDIT
As a followup to your comment, here is a simple example of how and where you could access this.props outside the render method:
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: this.props.count // acess props
};
this.add = this.add.bind(this);
this.sub = this.sub.bind(this);
}
componentWillReceiveProps(nextProps) {
this.setState({ count: nextProps.count });
}
add() {
this.props.addClick(); // acess props
}
sub() {
this.props.subClick(); // acess props
}
render() {
const { count } = this.state;
return (
<div>
<div>Count:{count} </div>
<button onClick={this.add}>+</button>
<button onClick={this.sub}>-</button>
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
this.addClick = this.addClick.bind(this);
this.subClick = this.subClick.bind(this);
}
addClick() {
const nextstate = this.state.count + 1;
this.setState({ count: nextstate });
}
subClick() {
const nextstate = this.state.count - 1;
this.setState({ count: nextstate });
}
render() {
return (
<div>
<h2>Wellcome to my Counter!</h2>
<Counter
count={this.state.count}
addClick={this.addClick}
subClick={this.subClick}
/>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Using different state in a nested object in two same component - reactjs

I would like to ask if how to dispatch or catch the data in mapStateToProps if data that I want to get is in a nested state and the identifier would be the this.props.group that is passed in FilmList via the Parent Component.
// Parent Component
<Row>
<FilmList group="upcoming" groupTitle="Upcoming Movies" />
<FilmList group="top_rated" groupTitle="Top Rated Movies" />
</Row>
// Child Component
class FilmList extends React.Component {
constructor(props){
super(props);
}
componentDidMount(){
this.props.getMovieByGroup(this.props.group);
}
renderFilmItem(){
if(this.props.data){
var film = this.props.data.upcoming.slice(0,6).map((item) => {
return <FilmItem key={item.id} film={item} />
});
return film;
}
}
render(){
console.log('new');
return(
<div className={styles.filmContainer}>
<h1>{ this.props.groupTitle }</h1>
{ this.renderFilmItem() }
</div>
);
}
}
function mapStateToProps(state){
return {
data: state.film.data.upcoming
}
}
This is what my state looks like:
This is my reducer:
const INITIAL_STATE = {
data: {},
error: {},
};
function processData(initialData, data) {
let updated = initialData;
updated[data.group] = data.results;
return updated;
}
export default (state = INITIAL_STATE, action) => {
switch(action.type) {
case GET_FILM_SUCCESS: {
return Object.assign({}, state.data[action.data.group], {
data: processData(state.data,action.data)
});
}
case GET_FILM_FAILURE: {
return { ...state, error: action.data }
}
}
return state;
}
Currently in my mapStateToProps I only access state.film.data.upcoming what I want to achieve is like state.film.data.{this.props.group} somewhere along that code so it will re render the component when "top_rated" or "upcoming" data state change.
So if state.file.data.upcoming is working fine, then you should be able to use state.file.data in mapStateToProps then do state.file.data[this.props.group] in your component.

React re-renders whole app after rendering a component

I use react and redux in my web app. It's the simple app which has 4 components, one reducer and 3 actions. After I add a new entry to list, react renders component of list (the listItem), then re-renders the whole app. What is the cause of re-rendering whole app after rendering one component?
Updated:
App container:
class App extends Component {
static propTypes = {
groups: PropTypes.array.isRequired,
actions: PropTypes.object.isRequired
};
render() {
return (<div>
<Header addGroup={this.props.actions.addGroup} />
<List groups={this.props.groups} />
</div>
);
}
}
function mapStateToProps(state) {
return { groups: state.groups };
}
function mapDispatchToProps(dispatch) {
return { actions: bindActionCreators(AppActions, dispatch) };
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
Reduser:
export default function groupDiseases(state = initialState, action){
switch (action.type) {
case ADD_GROUP:
return [
{
id: '',
name: action.name
},
...state
];
case DELETE_GROUP:
return state.filter(group =>
group.id !== action.id
);
case EDIT_GROUP:
return state.map(group => (group.id === action.id ? { id: action.id, name: action.name } : group));
default:
return state;
}
}
Components:
export default class Add extends Component {
static propTypes = {
addGroup: PropTypes.func.isRequired
}
componentDidMount() {
this.textInput.focus();
}
handleAdd = () => {
const name = this.textInput.value.trim();
if (name.length !== 0) {
this.props.addGroup(name);
this.textInput.value = '';
}
}
render() {
return (
<form className="add_form">
<input
type="text"
className="add__name"
defaultValue=""
ref={(input) => this.textInput = input}
placeholder="Name" />
<button
className="add__btn"
ref="add_button"
onClick={this.handleAdd}>
Add
</button>
</form>
);
}
}
export default class ListGroups extends Component {
static propTypes = {
groups: PropTypes.array.isRequired
};
render() {
let data = this.props.groups;
let groupTemplate = <div> Группы отсутствуют. </div>;
if (data.length) {
groupTemplate = data.map((item, index) => {
return (
<div key={index}>
<Item item={item} />
</div>
);
});
}
return (
<div className="groups">
{groupTemplate}
<strong
className={'group__count ' + (data.length > 0 ? '' : 'none')}>
Всего групп: {data.length}
</strong>
</div>
);
}
}
It's likely due to the fact that you are letting the <form> continue its default behavior, which is to submit to a targeted action. Take a look at the w3c spec for buttons:
http://w3c.github.io/html-reference/button.html
Specifically, a button with no type attribute will default to submit.
So your button is telling the form to submit, with the target being the current page since none is provided. In your handleAdd method, you can do something like:
handleAdd = (event) => {
event.preventDefault(); // prevent default form submission behavior
const name = this.textInput.value.trim();
if (name.length !== 0) {
this.props.addGroup(name);
this.textInput.value = '';
}
}
Or you can modify your button to have type="button".

Resources