Open ghost opened 8 years ago
Yeah, definitely coming. Would appreciate any help if you're up for submitting a PR! :+1:
hi, @mxstbr / @OpenKerley
I'm attempting to do this as my first foray into React dev. If I can get it all working, I'll submit the PR.
I'm currently over my head and and guidance is appreciated. Here is what I've done so far.
const selectState = () => {
return (state) => {
console.log("selectors.js : returning state",state);
return {
data: state
};
};
};
export default connect(createSelector(
selectState(),
selectRepos(),
selectUsername(),
selectLoading(),
selectError(),
(state, repos, username, loading, error) => ({ state, repos, username, loading, error })
), mapDispatchToProps)(HomePage);
I can click the button and navigate to the LoginPage, but don't understand how to get the state injected into the render. "this.props.data" is undefined in the render function of LoginPage/index.js
What am I missing here?
My guess is that I'm doing something wrong in how/where I'm injecting the state.
I see the state function in login-flow that creates the data json object .. do I need to add that to the connect function of any page that will navigate to LoginPage?
Below is the console ouput .. and I'm printing out this.props.data
This all looks good at first glance, did you connect
the LoginPage
component? (similarly to the HomePage one)
As a hack, I created the object in the render() function as below. This atleast has the page rendering.
//const { formState, currentlySending } = this.props.data;
const formState = {
username: '',
password: ''
}
const currentlySending = false;
I'm connecting the LoginPage at the bottom of LoginPage/index.js..as below
// Which props do we want to inject, given the global state?
function selectData(state) {
console.log("LoginPage state function",state);
return {
data: state,
};
}
// Wrap the component to inject dispatch and state into it
export default connect(selectData)(LoginPage);
I also
The LoginPage renders, and styles are there. There is a problem with the field inputs that I'm now looking into. When I type, I get the below.
I added thunk and looks like that resolved the above issue.
where is the appropriate place for the actions and constants to be?
the login-flow demo has them in the app component .. should they remain there? or should the be in the LoginPage or LoginForm?
So I think I'm going to have to focus on figuring out the data injection issue.
Because render() gets recalled after the form state changes, and I'm hardcoding the data to be empty strings, the text inputs are getting cleared each time I type into the input.
I added thunk and looks like that resolved the above issue. Where is the appropriate place for the actions and constants to be?
Instead of using thunked actions, move those asynchronous actions to redux-saga
.
Because render() gets recalled after the form state changes, and I'm hardcoding the data to be empty strings, the text inputs are getting cleared each time I type into the input.
Those'll have to live in the redux state!
thanks for the help.
I'm working on the state stuff now.
the code below taken from login-flow does not populate the data prop ( works in login-flow ) I'm initializing the component the same way that the boilerplate HomePage does.
Couple of Questions 1) Any recommendations on where to debug this props issue? The only difference between login-flow seems to be how the route is being setup .. should I be using reselect for this? 2) Which async actions should be sagas? The actual HTTP request, or other actions, too?
function select(state) {
return {
data: state
};
}
// Wrap the component to inject dispatch and state into it
export default connect(select)(LoginPage);
I've gotten things mostly working. Tomorrow or later this week I'll write up what all I've done. I'll need some input as to whether I'm doing things correctly, but I'm now attempting auth.login .. where do I find th e request.js? I tried pasting in the request.js from login-flow, but it doesn't work.
here are some of the things I had to do to get things partially working please take a look and see if you can identify some bad practices. the saga code is from me looking at https://github.com/yelouafi/redux-saga/tree/master/examples/real-world
for brevity I omitted the API call files.
a couple of things I'm unsure about.
1) am I dispatching the event to the saga correctly from LoginPage/index.js?
2) am I using the selectors correctly when connect()
ing the LoginPage/index.js?
3) can/should the reducer code be optimized or changed?
4) when completing async work in a saga, how do I return flow to the app?
Using the existing boilerplate code, I was unable to inject state props into my render()
method
To get the props injected, I had to change the LoginPage reducer function as below
function loginPageReducer(state = initialState, action) {
switch (action.type) {
case CHANGE_FORM:
var streamMap = {};
for (var i in action.stream_data) {
var item = action.stream_data[i];
streamMap[item.id] = item;
}
return state.merge(fromJS(streamMap).toOrderedMap());
break;
case SET_AUTH:
return assign({}, state, {
loggedIn: action.newState
});
break;
default:
return state;
}
}
changed boilerplate connect()
added selector and mapStateToProps for dispatch
/*
* LoginPage
*
* Users login on this page
* Route: /login
*
*/
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Form from 'components/LoginForm';
import auth from 'utils/auth';
import { login } from './actions';
import LoadingIndicator from 'components/LoadingIndicator';
import { createSelector } from 'reselect';
import { LOGIN } from './constants';
import {
selectState,
} from './selectors';
import styles from './styles.css';
export default class LoginPage extends React.Component {
render() {
const dispatch = this.props.dispatch;
// unable to access the formState using dot-notation .. I think this is related to Immutable.fromJS() in the reducer
const formState = this.props.myTestState.data.get('formState');
const currentlySending = this.props.myTestState.data.get('currentlySending');
return (
<div className={styles.formPageWrapper}>
<div className={styles.formPageFormWrapper}>
<div className={styles.formPageFormHeader}>
<h2 className={styles.formPageFormHeading}>Login</h2>
</div>
{/* While the form is sending, show the loading indicator,
otherwise show "Log in" on the submit button */}
<Form data={formState} dispatch={dispatch} location={location} history={this.props.history} onSubmit={::this._login} btnText={"Login"} currentlySending={currentlySending}/>
</div>
</div>
);
}
_login(username, password) {
console.log('LoginPage/index.js : _login()',username,password);
this.props.dispatch(login({
username: username,
password: password
}));
}
}
// Which props do we want to inject, given the global state?
function select(state) {
return {
data: state
};
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
dispatch
};
};
export default connect(createSelector(
selectState(),
(myTestState) => ({ myTestState })
), mapDispatchToProps)(LoginPage);
export const CHANGE_FORM = 'app/LoginPage/CHANGE_FORM';
export const SET_AUTH = 'app/LoginPage/SET_AUTH';
export const SENDING_REQUEST = 'app/LoginPage/SENDING_REQUEST';
export const LOGIN = 'app/LoginPage/LOGIN';
/*
*
* LoginPage actions
*
*/
import {
SET_AUTH,
CHANGE_FORM,
SENDING_REQUEST,
LOGIN
} from './constants';
/**
* event for LOGIN dispatch
*
*/
export function login(formState) {
return { type: LOGIN, formState };
}
/**
* Sets the authentication state of the application
* @param {boolean} newState True means a user is logged in, false means no user is logged in
*/
export function setAuthState(newState) {
return { type: SET_AUTH, newState };
}
/**
* Sets the form state
* @param {object} newState The new state of the form
* @param {string} newState.username The new text of the username input field of the form
* @param {string} newState.password The new text of the password input field of the form
* @return {object} Formatted action for the reducer to handle
*/
export function changeForm(newState) {
//console.log("LoginPage/actions.js : changeForm : newState : ",newState);
return { type: CHANGE_FORM, newState };
}
/**
* Sets the requestSending state, which displays a loading indicator during requests
* @param {boolean} sending The new state the app should have
* @return {object} Formatted action for the reducer to handle
*/
export function sendingRequest(sending) {
return { type: SENDING_REQUEST, sending };
}
/**
* Forwards the user
* @param {string} location The route the user should be forwarded to
*/
function forwardTo(location) {
console.log('forwardTo(' + location + ')');
browserHistory.push(location);
}
let lastErrType = "";
/**
* Called when a request failes
* @param {object} err An object containing information about the error
* @param {string} err.type The js-form__err + err.type class will be set on the form
*/
function requestFailed(err) {
// Remove the class of the last error so there can only ever be one
removeLastFormError();
const form = document.querySelector('.form-page__form-wrapper');
// And add the respective classes
form.classList.add('js-form__err');
form.classList.add('js-form__err-animation');
form.classList.add('js-form__err--' + err.type);
lastErrType = err.type;
// Remove the animation class after the animation is finished, so it
// can play again on the next error
setTimeout(() => {
form.classList.remove('js-form__err-animation');
}, 150);
}
/**
* Removes the last error from the form
*/
function removeLastFormError() {
const form = document.querySelector('.form-page__form-wrapper');
form.classList.remove('js-form__err--' + lastErrType);
}
/**
* Checks if any elements of a JSON object are empty
* @param {object} elements The object that should be checked
* @return {boolean} True if there are empty elements, false if there aren't
*/
function anyElementsEmpty(elements) {
for (let element in elements) {
if (!elements[element]) {
return true;
}
}
return false;
}
import { take, call, put, select, race } from 'redux-saga/effects';
import { LOGIN } from 'containers/LoginPage/constants';
import { LOCATION_CHANGE } from 'react-router-redux';
import { getCreds } from 'containers/LoginPage/selectors';
import bcrypt from 'bcryptjs';
import genSalt from 'utils/salt';
import auth from 'utils/auth';
import { api, history } from 'services';
import * as actions from './actions';
// each entity defines 3 creators { request, success, failure }
const { user, session } = actions
/***************************** Subroutines ************************************/
// resuable fetch Subroutine
// entity : user | repo | starred | stargazers
// apiFn : api.fetchUser | api.fetchRepo | ...
// id : login | fullName
// url : next page url. If not provided will use pass it to apiFn
function* fetchEntity(entity, apiFn, id, url) {
console.log("fetchEntity");
yield put( entity.request(id) )
const {response, error} = yield call(apiFn, url || id)
if(response)
yield put( entity.success(id, response) )
else
yield put( entity.failure(id, error) )
}
// yeah! bind Generators
export const loginUser = fetchEntity.bind(null, session, api.loginUser)
export default function* root(getState) {
}
export function* login(action) {
while (true) { // eslint-disable-line no-constant-condition
const watcher = yield race({
login: take(LOGIN),
stop: take(LOCATION_CHANGE), // stop watching if user leaves page
});
if (watcher.stop) break;
const creds = yield select(getCreds());
yield call(loginUser, "");
}
}
// All sagas to be loaded
export default [
defaultSaga,
login
];
// Individual exports for testing
export function* defaultSaga() {
}
Hey @lhammond,
I'm trying to get this up and running within the latest version of the boilerplate as well. Are you still using thunk? I've followed along and am stuck at re-working how it's handling the formState. Could you by any chance share your repo? Thanks for working on this.
@wullaski
I am no longer using thunk. I don't currently have the repo online, but should have it up for you early next week.
that said, everything I've done is above. you should be able to copy/paste all of that stuff.
I am also working on this... would be good to get it finalised. I will eventually.
Hey, I was wonding if you could bring this example up to date with the styles outlined in https://github.com/mxstbr/react-boilerplate/tree/v3.0.0/app