I'm trying to take control of state data of my component with one function. Im also using redux. But theres something wrong with redux or i don't see my mistake. Heres my component:
this.state = {
data: this.props.ui.users,
name: '',
email: ''
}
}
handleChange = (evt) => {
this.setState({ [evt.target.name]: evt.target.value });
}
componentWillReceiveProps = () => {
this.setState({
name: ''
})
}
render() {
return (
<div>
<form onSubmit={(e) => this.props.uiActions.addUser(e, this.state.name, this.state.email)}>
<input type="text"
name="name"
value={this.state.name}
onChange={this.handleChange}/>
</form>
</div>
)
}
}
Everything works. But when i want to add another input that handles email input, action doesnt triggers. I can pass without any problem this.state.email with my data, for example "something" but redux doesnt see another input. With one input everything is fine.
The difference is that below first input i add
<input type="text"
name="email"
value={this.state.email}
onChange={this.handleChange}/>
and redux doesnt triggers action. Heres action that handles passing data:
export function addUser(e, name, email) {
return (dispatch, getState) => {
e.preventDefault()
console.log(email)
const { users } = getState().ui
dispatch({ type: UI_ACTIONS.ADD_USER, users:[...users, {id: users.length+1, name: name, email: email}] });
}
}
reducer:
export default (state = initialState, action) => {
switch (action.type) {
case UI_ACTIONS.SET_REPOS:
return { ...state, users: action.users };
case UI_ACTIONS.ADD_USER:
return {...state, users: action.users};
default:
return state;
}
};
What am i doing wrong? Here you can find my repo: https://github.com/KamilStaszewski/crudapp/tree/develop/src
There's a lot going on here that really needs to be revised. The way you've structured the app is anti-pattern (non-stardard/bad practice) and will cause you more headaches as the application becomes more dynamic.
Several things to consider:
You don't need Redux unless you're using heavily nested components (for this example, React state would be sufficient)
You should separate your containers (Redux connected functions/AJAX requests) from your components (any function that cares about how things look): https://medium.com/#dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0
Keep form actions within the form's component (like e.preventDefault();) and utilize Redux state from within the reducer (you have access to Redux state within the reducer, so for the addUser action, there's no need to call Redux's getState();).
You don't need to dispatch an action if you're only returning the type and payload.
Always .catch() your promises. For some reason, there's a trend that up-and-coming developers assume that every promise will resolve and never throw an error. Not catching the error will break your app!
I've gone ahead and restructured the entire app. I encourage you to deconstruct it and follow the application flow, and then take your project and fix it accordingly.
Working example: https://codesandbox.io/s/zn4ryqp5y4
actions/index.js
import { UI_ACTIONS } from "../types";
export const fetchUsers = () => dispatch => {
fetch(`https://jsonplaceholder.typicode.com/users`)
.then(resp => resp.json())
.then(data => dispatch({ type: UI_ACTIONS.SET_REPOS, payload: data }))
.catch(err => console.error(err.toString()));
};
/*
export const handleNameChange = value => ({
type: UI_ACTIONS.UPDATE_NAME,
val: value
})
*/
/*
export const handleEmailChange = value => ({
type: UI_ACTIONS.UPDATE_EMAIL,
val: value
})
*/
export const addUser = (name, email) => ({
type: UI_ACTIONS.ADD_USER,
payload: { name: name, email: email }
});
components/App.js
import React from "react";
import UserListForm from "../containers/UserListForm";
export default ({ children }) => <div className="wrapper">{children}</div>;
components/displayUserList.js
import map from "lodash/map";
import React from "react";
export default ({ users }) => (
<table>
<thead>
<tr>
<th>ID</th>
<th>USER</th>
<th>E-MAIL</th>
</tr>
</thead>
<tbody>
{map(users, ({ id, name, email }) => (
<tr key={email}>
<td>{id}</td>
<td>{name}</td>
<td>{email}</td>
</tr>
))}
</tbody>
<tfoot />
</table>
);
containers/UserListForm.js
import map from "lodash/map";
import React, { Component } from "react";
import { connect } from "react-redux";
import { addUser, fetchUsers } from "../actions/uiActions";
import DisplayUserList from "../components/displayUserList";
class Userlist extends Component {
state = {
name: "",
email: ""
};
componentDidMount = () => {
this.props.fetchUsers();
};
handleChange = evt => {
this.setState({ [evt.target.name]: evt.target.value });
};
handleSubmit = e => {
e.preventDefault();
const { email, name } = this.state;
if (!email || !name) return;
this.props.addUser(name, email);
this.setState({ email: "", name: "" });
};
render = () => (
<div style={{ padding: 20 }}>
<h1 style={{ textAlign: "center" }}>Utilizing Redux For Lists</h1>
<form style={{ marginBottom: 20 }} onSubmit={this.handleSubmit}>
<input
className="uk-input"
style={{ width: 300, marginBottom: 10 }}
type="text"
name="name"
placeholder="Add user's name..."
value={this.state.name}
onChange={this.handleChange}
/>
<br />
<input
className="uk-input"
style={{ width: 300, marginBottom: 10 }}
type="text"
name="email"
placeholder="Add user's email..."
value={this.state.email}
onChange={this.handleChange}
/>
<br />
<button className="uk-button uk-button-primary" type="submit">
Submit
</button>
</form>
<DisplayUserList users={this.props.users} />
</div>
);
}
export default connect(
state => ({ users: state.ui.users }),
{ addUser, fetchUsers }
)(Userlist);
reducers/index.js
import { routerReducer as routing } from "react-router-redux";
import { combineReducers } from "redux";
import { UI_ACTIONS } from "../types";
const initialState = {
users: [],
name: "",
email: ""
};
const uiReducer = (state = initialState, { payload, type }) => {
switch (type) {
case UI_ACTIONS.SET_REPOS:
return { ...state, users: payload };
case UI_ACTIONS.ADD_USER:
return {
...state,
users: [...state.users, { id: state.users.length + 1, ...payload }]
};
default:
return state;
}
};
const rootReducer = combineReducers({
ui: uiReducer,
routing
});
export default rootReducer;
root/index.js
import React from "react";
import { browserHistory, Router } from "react-router";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import { syncHistoryWithStore } from "react-router-redux";
import thunk from "redux-thunk";
import rootReducer from "../reducers";
import routes from "../routes";
// CONFIG REDUX STORE WITH REDUCERS, MIDDLEWARES, AND BROWSERHISTORY
const store = createStore(rootReducer, applyMiddleware(thunk));
const history = syncHistoryWithStore(browserHistory, store);
// APP CONFIG'D WITH REDUX STORE, BROWSERHISTORY AND ROUTES
export default () => (
<Provider store={store}>
<Router
onUpdate={() => window.scrollTo(0, 0)}
history={history}
routes={routes}
/>
</Provider>
);
routes/index.js
import React from "react";
import { IndexRoute, Route } from "react-router";
import App from "../components/App";
import UserListForm from "../containers/UserListForm";
export default (
<Route path="/" component={App}>
<IndexRoute component={UserListForm} />
</Route>
);
types/index.js
export const UI_ACTIONS = {
UPDATE_NAME: "UPDATE_NAME",
INCREMENT_COUNT: "INCREMENT_COUNT",
SET_REPOS: "SET_REPOS",
ADD_USER: "ADD_USER",
UPDATE_NAME: "UPDATE_NAME",
UPDATE_EMAIL: "UPDATE_EMAIL"
};
export const TEST_ACTION = {
ACTION_1: "ACTION_1"
};
index.js
import React from "react";
import { render } from "react-dom";
import App from "./root";
import "uikit/dist/css/uikit.min.css";
render(<App />, document.getElementById("root"));
One thing I see that seems wrong to me is: input element isn't bind to this
<input type="text" name="name" value={this.state.name}
onChange={this.handleChange.bind(this)}
/>
Have you also trace what your event handler is doing in console?
Related
I am getting this error and I don;t know what else to do.
I am using next.js and my code looks like this.
The _app.js:
import '../styles/globals.scss'
import React from 'react'
import Layout from '../components/Layout'
import Head from "next/head";
import Signin from "./signin";
import Register from "./register";
import { DataProvider } from "../store/GlobalState";
function MyApp ({
Component,
pageProps
}) {
if (typeof window !== 'undefined') {
if (window.location.pathname === '/signin') {
return (
<DataProvider>
<Signin/>
</DataProvider>
)
} else if (window.location.pathname === '/register') {
return (
<DataProvider>
<Register/>
</DataProvider>
)
}
}
return (
<DataProvider>
<Head>
<title>Above the Sky</title>
</Head>
<Layout>
<Component {...pageProps} />
</Layout>
</DataProvider>
)
}
export default MyApp
I am doing this because I want the register and the login pages to be separate from the layout, not having any header or footer whatsoever... If you have a hint on this , how I should do this better please tell me .... but this is not the main problem..
and now the Register.js:
import Head from 'next/head'
import { useContext, useEffect, useState } from "react";
import Link from 'next/link'
import valid from '../utils/valid'
import { DataContext } from "../store/GlobalState";
const Register = () => {
const [ mounted, setMounted ] = useState(false);
const initialState = {
email: '',
password: '',
cf_password: ''
};
const [ userData, setUserData ] = useState(initialState);
const {
email,
password,
cf_password
} = userData;
const {
state,
dispatch
} = useContext(DataContext)
const handleChangeInput = e => {
const {
name,
value
} = e.target
setUserData({
...userData,
[name]: value
})
dispatch({
type: 'NOTIFY',
payload: {}
})
}
const handleSubmit = async e => {
e.preventDefault()
const errorMessage = valid(email, password, cf_password)
if (errorMessage) {
return dispatch({
type: 'NOTIFY',
payload: { error: errorMessage }
})
}
dispatch({
type: 'NOTIFY',
payload: { success: 'Ok' }
})
}
useEffect(() => {
setMounted(true)
}, [])
return (
mounted
&&
<div style={{
backgroundColor: 'black',
height: '100vh'
}}>
<Head>
<title>Register Page</title>
</Head>
<div className="login-dark" style={{ height: "695px" }}>
<form className='container' onSubmit={handleSubmit}>
<div className="illustration"><i className="fas fa-thin fa-user-plus"/></div>
<div className="mb-3">
<label htmlFor="exampleInputEmail1" className="form-label">Email address</label>
<input type="email" className="form-control" id="exampleInputEmail1" aria-describedby="emailHelp"
name="email" value={email} onChange={handleChangeInput}/>
<div id="emailHelp" className="form-text">We'll never share your email with anyone else.</div>
</div>
<div className="mb-3">
<label htmlFor="exampleInputPassword1" className="form-label">Password</label>
<input type="password" className="form-control" id="exampleInputPassword1"
name="password" value={password} onChange={handleChangeInput}/>
</div>
<div className="mb-3">
<label htmlFor="exampleInputPassword2" className="form-label">Confirm Password</label>
<input type="password" className="form-control" id="exampleInputPassword2"
name="cf_password" value={cf_password} onChange={handleChangeInput}/>
</div>
<div className='button-container'>
<button type="submit" className="btn btn-primary btn-block">Register</button>
</div>
<a className="forgot" href="#">Forgot your email or password?</a>
<p className="have-account">Already have an account ? <Link href="/signin"><a style={{ color: 'crimson' }}>Login here</a></Link></p>
</form>
</div>
</div>
)
}
export default Register
When I render the register page I get this error in the console ..
"next-dev.js?3515:32 Warning: Did not expect server HTML to contain a in ."
These are my store files aswell:
Actions.js
export const ACTIONS = {
NOTIFY: 'NOTIFY',
AUTH: 'AUTH'
}
Reducer.js
import { ACTIONS } from './Actions';
const reducers = (state, action) => {
switch (action.type) {
case ACTIONS.NOTIFY:
return {
...state,
notify: action.payload
};
case ACTIONS.AUTH:
return {
...state,
auth: action.payload
};
default:
return state;
}
}
export default reducers
and the GlobalState.js
import { createContext, useReducer } from "react";
import reducers from "./Reducers";
export const DataContext = createContext()
export const DataProvider = ({ children }) => {
const initialState = {
notify: {},
auth: {}
}
const [ state, dispatch ] = useReducer(reducers, initialState)
const { cart, auth } = state
return (
<DataContext.Provider value={{
state,
dispatch
}}>
{children}
</DataContext.Provider>
)
}
I am trying to create a simple form app, where there will be a textarea input and a submit button. Where, if I type something in the textarea and then click submit, the text that I just typed will show under the button inside a tag. When im doing this without Redux, it works fine, even after when I use Redux partly meaning when I manage only one state (input field state) using Redux it works great. But when i make two reducers, and two dispatches then problem happens. Here are my codes.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Provider from 'react-redux/es/components/Provider';
import {
createStore,
applyMiddleware,
combineReducers,
} from 'redux';
import { getInput, getOutput } from './reducer';
import { createLogger } from 'redux-logger';
import App from './App';
import reportWebVitals from './reportWebVitals';
const rootReducer = combineReducers({
getInput,
getOutput,
});
const logger = createLogger();
const store = createStore(
rootReducer,
applyMiddleware(logger)
);
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
app.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {
changeInput,
postOutput,
} from './action';
import {
Form,
Button,
Container,
} from 'react-bootstrap';
const mapStateToProps = (state) => {
return {
input: state.getInput.input,
output: state.getOutput.output,
};
};
const mapDispatchToProps = (dispatch) => {
return {
handleInput: (event) =>
dispatch(changeInput(event.target.value)),
handleClick: (props) =>
dispatch(postOutput(props.output)),
};
};
class App extends Component {
// constructor() {
// super();
// this.state = {
// output: '',
// };
// }
// handleInput = (event) => {
// this.setState({ input: event.target.value });
// };
// handleClick = () => {
// this.setState({
// output: this.props.input,
// });
// };
render() {
return (
<div>
<Container>
{' '}
<Form>
<Form.Group controlId='exampleForm.ControlTextarea1'>
<div>
<div
style={{
display: 'flex',
justifyContent: 'center',
marginTop: '20px',
marginBottom: '10px',
}}>
<Form.Control
as='textarea'
rows={5}
placeholder='enter something here'
onChange={this.props.handleInput}
style={{ width: '500px' }}
/>
</div>
<div
style={{
display: 'flex',
justifyContent: 'center',
}}>
<Button
variant='primary'
onClick={this.props.handleClick}>
Submit
</Button>
</div>
</div>
</Form.Group>
</Form>
</Container>
<div
style={{
display: 'flex',
justifyContent: 'center',
}}>
<h1 value={this.props.input}>
{this.props.output}
</h1>
</div>
</div>
);
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
action.js
import {
CHANGE_INPUT_FIELD,
POST_OUTPUT,
} from './constant';
export const changeInput = (text) => ({
type: CHANGE_INPUT_FIELD,
payload: text,
});
export const postOutput = (text) => ({
type: POST_OUTPUT,
payload: text,
});
reducer.js
import {
CHANGE_INPUT_FIELD,
POST_OUTPUT,
} from './constant';
const initialStateInput = {
input: '',
};
const initialStateOutput = {
output: '',
};
export const getInput = (
state = initialStateInput,
action = {}
) => {
switch (action.type) {
case CHANGE_INPUT_FIELD:
return Object.assign({}, state, {
input: action.payload,
});
default:
return state;
}
};
export const getOutput = (
state = initialStateOutput,
action = {}
) => {
switch (action.type) {
case POST_OUTPUT:
return Object.assign({}, state, {
output: action.payload,
});
default:
return state;
}
};
constant.js
export const CHANGE_INPUT_FIELD =
'CHANGE_INPUT_FIELD';
export const POST_OUTPUT = 'POST_OUTPUT';
changeInput action must be handled inside the component there is no reason to dispatch an action and handle it with reducer because reducer is for managing shared states.
Can you specify what is the "problem"?
The problem is not with actions, you cannot see the value because the value is set to undefined
In App.js you have to pass the correct value
onClick={this.props.handleClick}> must change as onClick={this.props.handleClick(this.props)}> otherwise props will be equal to event object in the line handleClick: (props) => dispatch(postOutput(props.output))
Still you won't see the value in UI because the output value is set to '' because you are not setting the input value to the output value in reducer.
My suggestion there must be another action that fires when submit button is clicked and sets the current input value to the input, then fire getOutput
Everything Seems Fine to me, the Console logs work in the Action and in the Reducer but the Chrome Redux tools show no action being trigged and no state updates.
Here is my Component Class
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { SimpleGrid,Box,Select,Input,InputGroup,InputRightAddon,Text,Heading ,Button,NumberInput,NumberInputField,} from "#chakra-ui/core";
import { Footer } from '../../layout/Main/components';
import {submitUserInput} from '../../utils/actions/userInput'
import PropTypes from 'prop-types';
class UserInput extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this)
this.state = {
yieldStress:0,
thickness:0,
diameter:0,
pMAX:0
}
this.handleChange = this.handleChange.bind(this)
}
handleSubmit=(event)=>{
event.preventDefault();
console.log(this.state);
this.props.submitUserInput(this.state);
}
handleChange=(event)=>{
this.setState({[event.target.name]:event.target.value});
}
render(){
const materialsList = this.props.materials.map((material)=>(
<option value={material.yieldStress} key={material.id}>{material.name}</option>
));
return (
<Box>
<Heading as="h4" size="md">Cylinder Details</Heading>
<Text>{this.props.thickness}</Text>
<form onSubmit={this.handleSubmit} >
<Select placeholder="Select Material of Cylinder" isRequired name="yieldStress" onChange={this.handleChange}>
{materialsList}
</Select>
<SimpleGrid columns={2} spacing={8} padding="16px 0px">
<Box>
<InputGroup>
<InputRightAddon children="(mm)"/>
<Input type="number" placeholder="Thickness of Cylinder" roundedRight="0" isRequired name="thickness" onChange={this.handleChange}/>
</InputGroup>
</Box>
<Box>
<InputGroup>
<InputRightAddon children="(mm)"/>
<Input type="number" placeholder="Diameter of Cylinder" roundedRight="0" isRequired name="diameter"onChange={this.handleChange}/>
</InputGroup>
</Box>
</SimpleGrid>
<SimpleGrid columns={2} spacing={8} padding="0px 0px">
<Box>
<InputGroup>
<InputRightAddon children={<Text paddingTop="12px">(N/mm)<sup>2</sup></Text>} padding="0 4px"/>
<NumberInput precision={2} step={0.2} >
<NumberInputField placeholder="Maximum pressure " roundedRight="0" isRequired name="pMAX" onChange={this.handleChange}/>
</NumberInput>
</InputGroup>
</Box>
<Box>
<InputGroup>
<InputRightAddon children="(mm)"/>
<Input type="number" placeholder="Factor of Saftey" roundedRight="0" isRequired name="FOS"onChange={this.handleChange}/>
</InputGroup>
</Box>
</SimpleGrid>
<Box padding="8px 0" >
<Button variantColor="teal" float="right" border="0" type="submit">Submit</Button>
</Box>
</form>
<Footer />
</Box>
)
}
}
UserInput.protoTypes = {
submitUserInput: PropTypes.func.isRequired,
thickness: PropTypes.string.isRequired,
}
const mapStateToProps = (state) => ({
thickness: state.userInput.thickness
})
const mapDispatchToProps = dispatch => {
return {
submitUserInput: (userInput)=> dispatch(submitUserInput(userInput))
}}
export default connect(mapStateToProps, mapDispatchToProps)(UserInput)
Here is My Action File
import { SUBMIT_REQUEST_SENT,SUBMIT_SUCCESS,SUBMIT_FAILURE} from '../types'
export const submitUserInput = ({yieldStress, FOS, diameter,pMAX,thickness }) => async (dispatch) => {
dispatch(submitRequestSent());
try {
console.log("Printing object in action")
console.log({yieldStress, FOS, diameter,pMAX,thickness }) //This is Printed
dispatch({
type: SUBMIT_SUCCESS,
payload:{yieldStress, FOS, diameter,pMAX,thickness }
}
);
}
catch (error) {
dispatch({
type: SUBMIT_FAILURE,
payload: error.message
})
throw error;
}
}
export const submitRequestSent = () =>
({
type: SUBMIT_REQUEST_SENT,
})
And Here is the UserInput Reducer
import {
SUBMIT_REQUEST_SENT,SUBMIT_SUCCESS,SUBMIT_FAILURE
} from '../../actions/types'
const initialState = {
userInput:{
yieldStress:0,
FOS:0,
diameter:0,
pMAX:0,
thickness:0
}
}
export default function (state = initialState, action) {
switch (action.type) {
case SUBMIT_REQUEST_SENT:
return {
...state,
}
case SUBMIT_SUCCESS:
console.log("Submit has been sucessfull And below is the Payload"); //This is Printed
console.log(action.payload)//This is Printed
return {
...state,
userInput:action.payload
}
case SUBMIT_FAILURE:
return {
...state,
error: action.payload
}
default:
return state;
}
}
And my root Reducer
import {combineReducers} from 'redux';
import authReducer from '../authReducer'
import userInputReducer from '../userInputReducer';
export default combineReducers(
{
auth:authReducer,
userInput:userInputReducer
}
)
I will Appreciate any applicable solution/Explanation..thanks
Within your state you have a property called userInput, which is the state section controlled by userInputReducer. That section has its own property which is also called userInput that stores the actual data. It that what you intended?
If so, you need to map properties like this:
const mapStateToProps = (state) => ({
thickness: state.userInput.userInput.thickness
})
If not, you need to rework your userInputReducer and initialState to remove the double nesting.
Everything Seems Fine to me, the Console logs work in the Action and in the Reducer but the Chrome Redux tools show no action being trigged and no state updates.
This sounds to me like an issue with dispatching an asynchronous thunk action. Redux cannot handle these types of actions without the appropriate middleware. When you create the store, it should be something like this:
import reducer from "./reducer";
import {createStore, applyMiddleware} from "redux";
import thunk from "redux-thunk";
const store = createStore(reducer, applyMiddleware(thunk));
I copy and posted your reducer code with a store set up as above. I used a dummy component ( a button that increments thickness when clicked ) and I was not able to reproduce your issue. That leads me to believe that the issue is in the configuration of the store itself.
i am having issues with prime-react, i tried to connect it to my django api which i had done successfully b4 and it work but with prime-react the same code doesn't seem to work. i don't know if it is running react dom or this is just a bug. so got this warning message in my console. Move code with side effects to componentDidMount, and set initial state in the constructor.
Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. In React 17.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, you can run npx react-codemod rename-unsafe-lifecycles in your project source folder.
'''
LeadsForm
import React, { Component } from 'react'
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { addLead } from '../actions/leads';
import {InputTextarea} from 'primereact/inputtextarea';
import {InputText} from 'primereact/inputtext';
import { Button } from 'primereact/button';
export class LeadsForm extends Component {
state = {
name: '',
email: '',
message: ''
};
static propTypes = {
addLead: PropTypes.func.isRequired
};
onChange = e => this.setState({ [e.target.name]: e.target.value});
onSubmit = e => {
e.preventDefault();
const { name, email, message } = this.state;
const lead = { name, email, message };
this.props.addLead(lead);
this.setState({
name: "",
email: "",
message: ""
});
};
render() {
const { name, email, message } = this.state;
return (
<div className="card card-w-title">
<div className="p-grid ">
<h2>Add Lead</h2>
<form onSubmit={this.onSubmit}>
<div className="p-col-12">
<span className="p-float-label">
<InputText id="in"
name="name"
value={name}
onChange={this.onChange} size={50} />
<label htmlFor="in">Name</label>
</span>
</div>
<div className="p-col-12">
<span className="p-float-label">
<InputText id="in"
name="email"
value={email}
onChange={this.onChange} size={50}/>
<label htmlFor="in">Email</label>
</span>
</div>
<div className="p-col-12">
<span className="p-float-label">
<InputTextarea id="in" size={50} rows={5} cols={30}
name="message"
value={message}
onChange={this.onChange} />
<label htmlFor="in">Message</label>
</span>
</div>
<Button type = "submit" value="Submit" label="Save" style={{marginBottom:'10px'}} className="p-button-raised" />
{/* <button type="submit" className="btn btn-primary">
Submit
</button> */}
</form>
</div>
</div>
)
}
}
export default connect(null, { addLead })(LeadsForm);
// here is the actions/leads file
import axios from 'axios';
import { GET_LEADS, ADD_LEAD } from './types'
export const getLeads = () => dispatch => {
axios
.get("http://127.0.0.1:8000/api/leads/")
.then(res => {
dispatch({
type: GET_LEADS,
payload: res.data
});
}).catch(err => console.log(err));
};
// ADD LEADS
export const addLead = lead => dispatch => {
axios
.post("http://127.0.0.1:8000/api/leads/", lead)
.then(res => {
dispatch({
type: ADD_LEAD,
payload: res.data
});
}).catch(err => console.log(err));
}
//here is the action/types file
export const GET_LEADS = "GET_LEADS";
export const ADD_LEAD = "ADD_LEAD";
//reducers/leads
import { GET_LEADS, ADD_LEAD } from '../actions/types.js';
const initialState = {
leads: []
}
export default function (state = initialState, action){
switch(action.type){
case GET_LEADS:
return {
...state,
leads: action.payload
};
case ADD_LEAD:
return {
...state,
leads: [...state.leads, action.payload]
}
default:
return state;
}
}
//reducers/index
import { combineReducers } from 'redux';
import leads from './leads';
export default combineReducers({
leads
});
'''
as said, run the command npx react-codemod rename-unsafe-lifecycles and if you get error like this:
npm ERR cb() never called
then run this:
npm config set registry https://registry.npmjs.org/
and then again run this: npx react-codemod rename-unsafe-lifecycles
This should solve the problem.
There is a Login component
// #flow
import type {
TState as TAuth,
} from '../redux';
import * as React from 'react';
import Button from '#material-ui/core/Button';
import FormControl from '#material-ui/core/FormControl';
import Input from '#material-ui/core/Input';
import InputLabel from '#material-ui/core/InputLabel';
import Paper from '#material-ui/core/Paper';
import { withNamespaces } from 'react-i18next';
import { Link } from 'react-router-dom';
import {
connect,
} from 'react-redux';
import { Field, reduxForm } from 'redux-form';
import useStyles from './styles';
import { login } from '../../redux';
import { push } from 'connected-react-router';
const logo = './assets/images/logo.png';
const {
useEffect,
} = React;
type TInputProps = {
input: Object,
meta: {
submitting: boolean,
}
}
const UserNameInput = (props: TInputProps) => (
<Input
id="userName"
name="userName"
autoComplete="userName"
autoFocus
{...props}
{...props.input}
disabled={props.meta.submitting}
/>
);
const PasswordInput = (props: TInputProps) => (
<Input
name="password"
type="password"
id="password"
autoComplete="current-password"
{...props}
{...props.input}
disabled={props.meta.submitting}
/>
);
type TProps = {
t: Function,
login: Function,
handleSubmit: Function,
error: string,
submitting: boolean,
auth: TAuth,
}
// TODO: fix flow error inside
const Login = ({
t,
login,
handleSubmit,
error,
submitting,
auth,
}: TProps) => {
const classes = useStyles();
useEffect(() => {
if (auth) {
console.log('push', push);
push('/dashboard');
}
}, [auth]);
return (
<main className={classes.main}>
<Paper className={classes.paper}>
<img src={logo} alt="logo" className={classes.logo} />
<form
className={classes.form}
onSubmit={handleSubmit((values) => {
// return here is very important
// login returns a promise
// so redux-form knows if it is in submission or finished
// also important to return because
// when throwing submissionErrors
// redux-form can handle it correctly
return login(values);
})}
>
<FormControl margin="normal" required fullWidth>
<Field
name="userName"
type="text"
component={UserNameInput}
label={
<InputLabel htmlFor="userName">{t('Username')}</InputLabel>
}
/>
</FormControl>
<FormControl margin="normal" required fullWidth>
<Field
name="password"
type="password"
component={PasswordInput}
label={
<InputLabel htmlFor="password">{t('Password')}</InputLabel>
}
/>
</FormControl>
<div className={classes.error}>{error}</div>
<Button
disabled={submitting}
type="submit"
fullWidth
variant="outlined"
color="primary"
className={classes.submit}
>
{t('Sign in')}
</Button>
<Link className={classes.forgot} to="/forgot">
{t('Forgot Password?')}
</Link>
</form>
</Paper>
</main>
);
};
const mapStateToProps = ({ auth }) => ({ auth });
const mapDispatchToProps = {
login,
};
export default connect(
mapStateToProps,
mapDispatchToProps,
)(
reduxForm({ form: 'login' })(withNamespaces()(Login))
);
In the useEffect hook the push from connected-react-router is used. The hook fires ok but nothing happens after it.
The same way, push is used in login action.
// #flow
import type {
TReducer,
THandlers,
TAction,
TThunkAction,
} from 'shared/utils/reduxHelpers';
import type {
TUser,
} from 'shared/models/User';
import createReducer from 'shared/utils/reduxHelpers';
import urls from 'constants/urls';
import axios, { type $AxiosXHR } from 'axios';
import { SubmissionError } from 'redux-form';
import { push } from 'connected-react-router';
export type TState = ?{
token: string,
result: $ReadOnly<TUser>,
};
export const ON_LOGIN = 'ON_LOGIN';
export const login: ({ userName: string, password: string }) => TThunkAction =
({ userName, password }) => async (dispatch, getState) => {
const res: $AxiosXHR<{username: string, password: string}, TState> =
await axios.post(`${urls.url}/signin`, { username: userName, password })
.catch((err) => {
throw new SubmissionError({ _error: err.response.data.message });
});
const data: TState = res.data;
dispatch({
type: ON_LOGIN,
payload: data,
});
push('/dashboard');
};
const handlers: THandlers<TState, TAction<TState>> = {
[ON_LOGIN]: (state, action) => action.payload,
};
const initialState = null;
const reducer: TReducer<TState> = createReducer(initialState, handlers);
export default reducer;
Here everything goes successful and dispatch happens and there is no push happening again.
Whats the problem?
Shouldn't there be dispatch(push('/dashboard')); ?
You just have to make sure that you do not create your middleware and pass in the history api before calling createRootReducer function.
If you try to create your middleware with routerMiddleware(history) too early , history will be passed in as undefined. Follow the README.md as it explains the exact execution order.
// configureStore.js
...
import { createBrowserHistory } from 'history'
import { applyMiddleware, compose, createStore } from 'redux'
import { routerMiddleware } from 'connected-react-router'
import createRootReducer from './reducers'
...
export const history = createBrowserHistory()
export default function configureStore(preloadedState) {
const store = createStore(
createRootReducer(history), // <-- Initiates the History API
preloadedState,
compose(
applyMiddleware(
routerMiddleware(history), // <--- Now history can be passed to middleware
// ... other middlewares ...
),
),
)
return store
}