Pass data from component to action and saga using ARc/React/Redux - javascript

I'm using https://arc.js.org/ to setup a new project and am confused on how data is passed about. I'm just using a form to post some login data and can't seem to get the data to the post itself in my actions/sagas (meaning, my login form component has the data and tries to send it on, but after dispatching the action, value is undefined).
Intending to get the form values into redux store (updating onChange to be accessible onSubmit, not passing up as I am now), but wanted to get this version working first and then move on to that so I know what's actually happening.
Let me know if missing necessary info here.
LoginFormContainer:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { userLoginRequest } from 'store/actions'
import { fromUser } from 'store/selectors'
import { LoginForm } from 'components'
class LoginFormContainer extends Component {
static propTypes = {
login: PropTypes.func.isRequired,
}
onSubmit = (event) => {
event.preventDefault()
const serialize = new FormData(event.target)
const loginData = {
email: serialize.get('email'),
password: serialize.get('password'),
}
this.props.login(loginData)
}
render() {
return <LoginForm handleSubmit={this.onSubmit} />
}
}
const mapStateToProps = (state) => ({
user: fromUser.getUser(state),
})
const mapDispatchToProps = (dispatch, { loginData }) => ({
login: () => dispatch(userLoginRequest(loginData)),
})
export default connect(mapStateToProps, mapDispatchToProps)(LoginFormContainer)
LoginFormComponent:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import { Field } from 'components'
const Form = styled.form`
width: 100%;
`
const LoginForm = ({ handleSubmit }) => {
return (
<Form onSubmit={handleSubmit}>
<Field
label="Email"
name="email"
type="text"
/>
<Field
label="Password"
name="password"
type="text"
/>
<button type="submit">Login</button>
</Form>
)
}
LoginForm.propTypes = {
handleSubmit: PropTypes.func.isRequired,
handleChange: PropTypes.func.isRequired,
}
export default LoginForm
Actions:
export const USER_LOGIN_REQUEST = 'USER_LOGIN_REQUEST'
export const USER_LOGIN_SUCCESS = 'USER_LOGIN_SUCCESS'
export const USER_LOGIN_FAILURE = 'USER_LOGIN_FAILURE'
// This doesn't know what data is (undefined)
export const userLoginRequest = (data, resolve, reject) => ({
type: USER_LOGIN_REQUEST,
data,
resolve,
reject,
})
export const userLoginSuccess = detail => ({
type: USER_LOGIN_SUCCESS,
detail,
})
export const userLoginFailure = error => ({
type: USER_LOGIN_FAILURE,
error,
})
Sagas:
import { take, put, call, fork } from 'redux-saga/effects'
import api from 'services/api'
import * as actions from './actions'
// This doesn't know what loginData is (undefined)
export function* login(loginData) {
try {
const encoded = window.btoa(`${loginData.email}:${loginData.password}`)
const data = yield call(api.post, '/login', { Authorization: `Basic ${encoded}` })
yield put(actions.userLoginSuccess(data))
} catch (e) {
yield put(actions.userLoginFailure(e))
}
}
export function* watchUserLoginRequest() {
while (true) {
const { data } = yield take(actions.USER_LOGIN_REQUEST)
yield call(login, data)
}
}
export default function* () {
yield fork(watchUserLoginRequest)
}

Thanks to #dagatsoin for helping lead in right direction!
mapDispatchToProps should be:
const mapDispatchToProps = (dispatch) => ({
login: (loginData) => dispatch(userLoginRequest(loginData)),
})

Related

UseDispatch hook react-redux

Help me to understand. i use useDispatch but i don't change the state. what am I doing wrong. I read a lot of information, but I can not understand what is my mistake.
i tried other hooks but nothing works.
I've marked up the code below to make it clear. Reducer, action, store, component
component:
import React from 'react';
import {useDispatch} from "react-redux";
import {loginAction, passwordAction} from "../action/userAction";
import storeConfiguration from "../store/storeConfiguration";
import {changeLogin, changePassword} from "../utils/Const";
const Login = () => {
const dispatch = useDispatch()
const stateUser = storeConfiguration.getState()
const senData = () => {
localStorage.setItem(stateUser.login, stateUser.password)
console.log(storeConfiguration.getState());
let keys = Object.keys(localStorage);
for(let key of keys) {
console.log(`${key}: ${localStorage.getItem(key)}`);
dispatch(loginAction())
dispatch(passwordAction())
}
}
function clear() {
localStorage.clear();
}
return (
<div>
<p>Please, enter Username</p>
<input placeholder={'your login'}
onChange={e => changeLogin( e.target.value)}/>
<p>Please, enter Password</p>
<input placeholder={'your password'}
onChange={e => changePassword( e.target.value)}/>
<p></p>
<button onClick={()=>senData()}>Enter</button>
<button onClick={()=>clear()}>clear</button>
</div>
);
};
export default Login;
action:
export const LOGIN = 'loginAction'
export const PASSWORD = 'passwordAction'
export const loginAction = login =>(
{
type: LOGIN,
payload: login
})
export const passwordAction = password =>(
{
type: PASSWORD,
payload: password
})
reducer:
import {LOGIN, PASSWORD} from "../action/userAction";
function userReducer (state, action)
{
switch (action.type){
case LOGIN:
return {...state, login: action.payload }
case PASSWORD:
return {...state, password: action.payload }
default:
return state
}
}
export default userReducer
store:
import userReducer from "../reducer/userReducer";
import { legacy_createStore as createStore} from 'redux'
const initialState =
{
login:'',
password: '',
}
const store = createStore(userReducer, initialState)
export default store
const:
export const currentLogin = 'Admin'
export const currentPassword = '12345'
export const changeLogin = (login) => {
return login
}
export const changePassword = (password) => {
return password
}
In this two lines of code
dispatch(loginAction())
dispatch(passwordAction())
You haven't passed any payload, so nothing can be changed actually

Function is not getting called anymore, when trying to dispatch a type

I am currently trying to access my data using the Spotify API. This works very well. Thats the function I am using to get my Data. I assume the other stuff is not important. I can post that, if you need that.
export const getSpotifyUser = (access_token:string) =>{
setAuthorizationHeader(access_token)
axios.get('https://api.spotify.com/v1/me').then((res) => {
console.log(res.data)
})
}
I have set up a redux store and trying to put the credentials into the store, by dispatching the right type (SET_USER).
export const getSpotifyUser = (access_token:string) => (dispatch: any) => {
console.log("function is not called") // Function is not even called why ?
setAuthorizationHeader(access_token)
axios.get('https://api.spotify.com/v1/me').then((res) => {
console.log(res.data)
dispatch ({
type: SET_USER,
payload: res.data
})
}
but as soon as I use dispatch, the function is no longer called.
I really do not see my mistake. Is that a typescript error ?. ( I am using react typescript)
store.js
import { createStore, applyMiddleware } from 'redux'
import rootReducer from './rootReducer'
import { composeWithDevTools } from 'redux-devtools-extension'
import thunk from 'redux-thunk'
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(thunk))
)
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch
export default store
rootReducer.ts
import { combineReducers } from 'redux'
import userReducer from './User/userReducer'
const rootReducer = combineReducers({
user: userReducer,
})
export default rootReducer
userReducer.ts
import { AnyAction } from 'redux'
import { SET_USER } from './userTypes'
interface Credentials {
username: string
email: string
profilepicture: string
id: number
}
interface InitialState {
authenticated: boolean
loadding: boolean
credentials?: Credentials
}
const initialState: InitialState = {
authenticated: false,
loadding: false,
credentials: {} as Credentials,
}
const reducer = (state = initialState, action: AnyAction) => {
switch (action.type) {
case SET_USER: {
return {
...state,
loading: false,
credentials: action.payload,
}
}
default:
return state
}
}
export default reducer
Login.tsx ( I am making the login here. It is working fine, if am not using dispatch
import { IonButton } from '#ionic/react'
import React, { useEffect } from 'react'
import {
getAuthorizeHref,
getHashParams,
removeHashParamsFromUrl,
getSpotifyUser,
} from '../../Helpers/login'
const Login: React.FC = () => {
// const user = useSelector((state: RootState) => state.user.credentials)
useEffect(() => {
const hashParams = getHashParams()
const access_token = hashParams.access_token
// const expires_in = hashParams.expires_in
removeHashParamsFromUrl()
getSpotifyUser(access_token)
}, [])
return (
<IonButton onClick={() => window.open(getAuthorizeHref(), '_self')}>
)}
export default Login
since you're using typescript with react, I believe you have added the getSpotifyUser function to your interface, now if you want to access that i think you should call it like this
props.getSpotifyUser(access_token)
and finally add it to your connect as a dispatch function that's wrapping your component
your login component should be like this one
import { IonButton } from '#ionic/react'
import React, { useEffect } from 'react'
import { connect } from 'react-redux'
import {
getAuthorizeHref,
getHashParams,
removeHashParamsFromUrl,
getSpotifyUser,
} from '../../Helpers/login'
interface ILogin {
getAuthorizeHref: () => any;
getHashParams: () => any;
removeHashParamsFromUrl: () => any;
getSpotifyUser: (access_token) => any;
}
const Login: React.FC = (props: ILogin) => {
// const user = useSelector((state: RootState) => state.user.credentials)
useEffect(() => {
const hashParams = props.getHashParams()
const access_token = hashParams.access_token
// const expires_in = hashParams.expires_in
props.removeHashParamsFromUrl()
props.getSpotifyUser(access_token)
}, [])
return (
<IonButton onClick={() => window.open(props.getAuthorizeHref(), '_self')}>
)}
export default connect(null, {getAuthorizeHref, getHashParams, removeHashParamsFromUrl, getSpotifyUser})(Login)
Basicly Shamim has given the right answer.Any function that uses that dispatch is a redux action, and you have to follow the docs specifically to call that function. You have to use connect to dispatch actions. As an alternative you can use the dispatchHook. If am wrong please please correct me !!!!
Thats the right code I just had to correct Login.tsx
import { IonApp, IonButton } from '#ionic/react'
import React, { useEffect } from 'react'
import { connect } from 'react-redux'
import {
getAuthorizeHref,
getHashParams,
removeHashParamsFromUrl,
getSpotifyUser,
} from '../../Helpers/login'
const style = {
Logo: {
display: 'flex',
justifyContent: 'space-evenly',
color: 'white',
position: 'relative',
top: '70%',
} as const,
}
const Login: React.FC = (props: any) => {
// const user = useSelector((state: RootState) => state.user.credentials)
useEffect(() => {
const hashParams = getHashParams()
const access_token = hashParams.access_token
// const expires_in = hashParams.expires_in
removeHashParamsFromUrl()
console.log('halloeuseeffect')
props.getSpotifyUser(access_token)
console.log('halloeuseeffect')
}, [])
return (
<IonApp>
<IonButton onClick={() => window.open(getAuthorizeHref(), '_self')}>
knsnan
</IonApp>
)
}
export default connect(null, {
getSpotifyUser,
})(Login)

Error: Actions must be plain objects. Use custom middleware for async actions, in a delete button?

I am trying to get a react action to fetch a list of files after the user deletes a file from the list.
In App.js I pass a handleClick function to the nested component.
App.js
class App extends Component {
static propTypes = {
files: PropTypes.array.isRequired,
isFetching: PropTypes.bool.isRequired,
dispatch: PropTypes.func.isRequired,
handleClick : PropTypes.func
};
componentDidMount() {
const {dispatch} = this.props;
dispatch(fetchFiles);
}
handleClick = fileId => {
const {dispatch} = this.props;
deleteFileById(dispatch,fileId);
};
render() {
const {files, isFetching, dispatch} = this.props;
const isEmpty = files.length === 0;
return (
<div>
<h1>Uploadr</h1>
{isEmpty
? (isFetching ? <h2>Loading...</h2> : <h2>No files.</h2>)
: <div style={{opacity: isFetching ? 0.5 : 1}}>
<Files files={files} handleClick={this.handleClick}/>
</div>
}
</div>
)
}
}
const mapStateToProps = state => {
const {isFetching, items: files} = state.files;
return {
files,
isFetching,
}
};
export default connect(mapStateToProps)(App)
Files.js
import React from 'react'
import PropTypes from 'prop-types'
const Files = ({files, handleClick }) => (
<ul>
{files.map((file, i) =>
<li key={i}>{file.name}
<button onClick={() => (handleClick(file.id))}>Delete</button>
</li>
)}
</ul>
);
Files.propTypes = {
files: PropTypes.array.isRequired,
handleClick: PropTypes.func.isRequired
};
export default Files
actions.js
I am wanting to trigger a request to get a new list of files from the API after the delete action is done.
export const deleteFileById = (dispatch, fileId) => {
dispatch(deleteFile);
return fetch(`/api/files/${fileId}`, {method : 'delete'})
.then(dispatch(fetchFiles(dispatch)))
};
export const fetchFiles = (dispatch) => {
dispatch(requestFiles);
return fetch('/api/files')
.then(response => response.json())
.then(json => dispatch(receiveFiles(json)))
};
However I am getting the following error
Error: Actions must be plain objects. Use custom middleware for async actions.
What is the best way to implement this
An action will dispatch another action but not event handler function.
You no need to dispatch deleteFileById from component because this is a function exported in actions which will dispatch an action.
Please remove dispatch in handleClick to work.
Wrong one:
handleClick = fileId => {
this.props.deleteFileById(dispatch(this.props.dispatch,fileId));
};
Correct one:
handleClick = fileId => {
this.props.deleteFileById(this.props.dispatch,fileId);
};
Regarding this.props.deleteFileById is not a function.
There are many ways to access actions in your component. Below are few ways
You need to install prop-types
npm install -s prop-types
If your component is Test then set prop types as like below
import PropTypes from 'prop-types';
import React, {Component} from 'react';
class Test extends Component{
render(){
return(
<div</div>
)
}
}
Test.propTypes = {
deleteFileById: PropTypes.func
}
If you are using redux connect then
Without prop-types
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions';
class Test extends Component{
render(){
return(
<div</div>
)
}
}
export default connect(null, {...actions})(Test);
OR
With inbuilt react proptypes you no need to install prop-types separately
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions';
import {push} from 'react-router-redux';
class Test extends Component{
static get propTypes() {
return {
sendContactForm: React.PropTypes.func
}
}
render(){
return(
<div</div>
)
}
}
const actionsToProps = {
deleteFileById: actions.deleteFileById,
push
}
export default connect(null, actionsToProps)(Test);
Your code App.jsx should be something like below
class App extends Component {
static propTypes = {
files: PropTypes.array.isRequired,
isFetching: PropTypes.bool.isRequired,
deleteFileById : PropTypes.func,
fetchFiles: PropTypes.func
};
componentDidMount() {
this.props.fetchFiles();
}
handleClick = fileId => {
this.props.deleteFileById(fileId);
};
render() {
const {files, isFetching} = this.props;
const isEmpty = files.length === 0;
return (
<div>
<h1>Uploadr</h1>
{isEmpty
? (isFetching ? <h2>Loading...</h2> : <h2>No files.</h2>)
: <div style={{opacity: isFetching ? 0.5 : 1}}>
<Files files={files} handleClick={this.handleClick}/>
</div>
}
</div>
)
}
}
const mapStateToProps = state => {
const {isFetching, items: files} = state.files;
return {
files,
isFetching,
}
};
export default connect(mapStateToProps)(App)
dispatch should be returned in actions but not from component to actions or vice versa
Below is sample action file for your ref.
import ajax from '../ajax';
import {Map, fromJS} from 'immutable';
import config from '../config';
import {push} from 'react-router-redux'
export const URL_PREFIX = 'http://localhost:3000/api';
export const SEND_CONTACT_FORM_REQUEST = 'SEND_CONTACT_FORM_REQUEST';
export const SEND_CONTACT_FORM_SUCCESS = 'SEND_CONTACT_FORM_SUCCESS';
export const SEND_CONTACT_FORM_ERROR = 'SEND_CONTACT_FORM_ERROR';
export function sendContactFormRequest(){
return {
type: SEND_CONTACT_FORM_REQUEST,
loading: true
}
}
export function sendContactFormSuccess(data){
return {
type: SEND_CONTACT_FORM_SUCCESS,
loading: false,
data: data
}
}
export function sendContactFormError(errors){
return {
type: SEND_CONTACT_FORM_ERROR,
loading: false,
errors: errors
}
}
export function sendContactForm(firstName, lastName, email, subject, message) {
return dispatch => {
dispatch(sendContactFormRequest());
return ajax.post(URL_PREFIX + '/communication/contact', { firstName, lastName, email, subject, message })
.then(res => {
dispatch(sendContactFormSuccess(res.data))
})
.catch(errors => {
dispatch(sendContactFormError(errors))
})
}
}

_This2 not a function error when dispatching action

Background
I am working on a very routine chunk of code, I have created actions and reducers many times throughout my app. I am now setting up authentication, and have two containers loading based on routes / & /register.
Issue
I am trying to dispatch an action, and do a simple console.log("test"). I have done this many times before, in-fact, I have literally duplicated a container and altered the names of the dispatched action names. One container works, while the other is hitting me with:
Uncaught TypeError: _this2.propsregisterHandler is not a function
I am confused why its not showing a . between props and registerHandler
Here is the relevent code:
Container Import
import { register } from "../../store/actions/authentication";
JSX
<div
className="btn btn-primary col"
onClick={() =>
this.props.registerHandler(
this.state.email,
this.state.password
)
}
>
Register
</div>
....
Disptach Code
const mapStateToProps = state => {
return {};
};
const mapDisptachToProps = dispatch => {
return {
registerHandler: () => dispatch(register())
};
};
export default connect(
mapStateToProps,
mapDisptachToProps
)(Register);
The action
import * as actionTypes from "./actiontypes";
export const register = () => {
console.log("TEST");
return { type: actionTypes.REGISTER };
};
Reducer
const reducer = (state = initialState, action) => {
switch (action.type) {
case actiontypes.REGISTER: {
console.log("you called the reducer");
return state;
}
Revised
This code here does not work, I always get the error, however if I call the same action in my login component, it will work.
import React, { Component } from "react";
import { connect } from "react-redux";
import { registerUserToApp } from "../../store/actions/authentication";
import "../Login/login";
export class Register extends Component {
state = {
email: "",
password: ""
};
render() {
return (
<div
className="btn btn-primary"
onClick={() => {
this.props.registerUserToAppHandler();
}}
>
Register
</div>
);
}
}
const mapStateToProps = state => {
return {};
};
const mapDispatchToProps = dispatch => {
return {
registerUserToAppHandler: () => dispatch(registerUserToApp())
};
};
export default connect(
mapDispatchToProps,
mapStateToProps
)(Register);
login Component
import React, { Component } from "react";
import { connect } from "react-redux";
import Aux from "../../components/hoc/Aux";
import Logo from "../../assets/images/Logo.png";
import GoogleLogo from "../../assets/images/google.svg";
import {
loginUser,
loginUserWithGoogle,
registerUserToApp
} from "../../store/actions/authentication";
import "./login.css";
export class Login extends Component {
state = {
email: "",
password: ""
};
render() {
const userNameChangeHandler = event => {
this.setState({
email: event.target.value
});
};
const passworChangeHandler = event => {
this.setState({
password: event.target.value
});
};
return (
<Aux>
...
<div
className="btn btn-primary col"
onClick={() => {
this.props.loginUserHandler(
this.state.email,
this.state.password
);
this.props.registerUserToAppHandler();
}}
>
Sign In
</div>
...
</Aux>
);
}
}
const mapStateToProps = state => {
return {};
};
const mapDisptachToProps = dispatch => {
return {
loginUserHandler: (email, password) => dispatch(loginUser(email, password)),
registerUserToAppHandler: () => dispatch(registerUserToApp()),
loginUserWithGoogleHandler: () => dispatch(loginUserWithGoogle())
};
};
export default connect(
mapStateToProps,
mapDisptachToProps
)(Login);
I can't leave a comment, but shouldn't you add .css extension when importing styles?
import "../Login/login";
The issue was due to how I was loading this component into my container. I am nut sure of the exact reasoning but I was importing my component into the container using a named import import {Login} from ".../path", whereas it should have been import Login from ".../path".

Redux firing undefined action while using redux thunk

This issue likely stems from a misconfiguration of redux-thunk or a misunderstanding of how to write a thunk. I've tried a lot of different ways, but from what I can tell, this should work. However, I'm still getting a console message that says its firing a redux action of undefined.
Here is my store configuration
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
import App from './components/App';
import rootReducer from './reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={ store }>
<App />
</Provider>,
document.getElementById('rootElement')
);
Here is my action:
import axios from 'axios';
export const GET_ABOUT_CONTENT_REQUEST = 'GET_ABOUT_CONTENT_REQUEST';
export const GET_ABOUT_CONTENT_FAILED = 'GET_ABOUT_CONTENT_FAILED';
export const GET_ABOUT_CONTENT_OK = 'GET_ABOUT_CONTENT_OK';
export const fetchAboutContent = () => {
const url = `http://localhost:3000/about`;
return (dispatch, getState) => {
if (getState.isInitialized === true){
console.log("desktop init should not be called when already desktop is init")
return Promise.resolve();
}
if (getState.about.isLoading) {
console.log('is loading');
return Promise.resolve();
}
dispatch({ type: GET_ABOUT_CONTENT_REQUEST });
axios.get(url)
.then(res => dispatch({
type: GET_ABOUT_CONTENT_OK,
res
}))
.error(err => dispatch({
type: GET_ABOUT_CONTENT_FAILED,
err
}));
}
}
Here is me firing the action in my component:
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as actions from '../../actions/about';
import getAboutContent from '../../reducers';
class AboutMe extends React.Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.getAboutContent();
}
render() {
return <div>{ this.props.content }</div>
}
}
const mapStateToProps = (state) => ({
content: {} || getAboutContent(state)
})
const mapDispatchToProps = (dispatch) =>
bindActionCreators({ getAboutContent }, dispatch)
export default connect(
mapStateToProps, mapDispatchToProps
)(AboutMe);
I've tried quite a few configurations for mapDispatchToProps, i.e. connect(..., { fetchData: getAboutContent })..., and more. Any help is greatly appreciated.
Edit:
Here is the git repo, if that is helpful to anybody: https://github.com/sambigelow44/portfolio-page
Check your reducer name,you export fetchAboutContent, but import getAboutContent.
Code in action file is seems to be incorrect.
getState is a function.
const state = getState();
Change below code.
import axios from 'axios';
export const GET_ABOUT_CONTENT_REQUEST = 'GET_ABOUT_CONTENT_REQUEST';
export const GET_ABOUT_CONTENT_FAILED = 'GET_ABOUT_CONTENT_FAILED';
export const GET_ABOUT_CONTENT_OK = 'GET_ABOUT_CONTENT_OK';
export const fetchAboutContent = () => {
const url = `http://localhost:3000/about`;
return (dispatch, getState) => {
if (getState().isInitialized === true){
console.log("desktop init should not be called when already desktop is init")
return Promise.resolve();
}
if (getState().about.isLoading) {
console.log('is loading');
return Promise.resolve();
}
dispatch({ type: GET_ABOUT_CONTENT_REQUEST });
axios.get(url)
.then(res => dispatch({
type: GET_ABOUT_CONTENT_OK,
res
}))
.error(err => dispatch({
type: GET_ABOUT_CONTENT_FAILED,
err
}));
}
}
Also you need to return promise from axios call, just add return statement.
return axios.get(url)
.then(res => dispatch({
type: GET_ABOUT_CONTENT_OK,
res
}))
.error(err => dispatch({
type: GET_ABOUT_CONTENT_FAILED,
err
}));

Categories