Props bugs with React Router - javascript

I' learning to work with Redux, React & React Router.
I've face with such a problem:
When I redirect from "/" to the URL like "/details/{id}" using Link - I see that a wrong action creator is called. Action creator from "/" component is called indead of component's one in "/details/{id}.
But if I refresh my page, everything will be fine. Correct action is called.
Routing with Link: <Link to={/details/${this.props.movie.id}}>
Other bug: if I press "Back" from this page to return to "/", I will get an error, that my props' data are undefined.
Like, props are empty and the action creator for http-request is not called (?) from componentDidMount().
But if I refresh my page again, everything will be fine again.
What's wrong with routing?? Or redux?
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<Routes />
</BrowserRouter>
</Provider>,
document.getElementById('root'));
const Routes = () => {
return <div>
<Route exact path="/" component={Main} />
<Route path="/details/:id" component={MovieDetailsContainer} />
</div>;
}
"/":
class MoviesDiscover extends Component {
componentDidMount() {
this.props.dicoverMovies();
}
render() {
...
}
}
const mapStateToProps = (state) => {
return {
items: state.movieItems,
hasErrored: state.movieHasErrored,
isLoading: state.movieIsLoading,
};
};
const mapDispatchToProps = (dispatch) => {
return {
dicoverMovies: (url) => dispatch(dicoverMovies(url))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(MoviesDiscover);
"details/{id}":
class MovieDetailsContainer extends Component {
componentDidMount() {
var id = this.props.match.params.id;
this.props.getMovie(id); // called from MoviesDiscover instead of this, after routing
}
render() {
var id = this.props.match.params.id;
...
}
}
const mapStateToProps = (state) => {
return {
item: state.movieItems,
hasErrored: state.movieHasErrored,
isLoading: state.movieIsLoading,
};
};
const mapDispatchToProps = (dispatch) => {
return {
getMovie: (url) => dispatch(getMovieDetails(url))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(MovieDetailsContainer);
Actions (the same for two reqests - actions for results, error and lodaing):
...
export function moviesDataSuccess(items) {
return {
type: MOVIES_RESULTS,
isLoading: false,
items: items
};
}
export function dicoverMovies() {
return (dispatch) => {
dispatch(moviesIsLoading(true));
API.get(URL_MOVIE_DISCOVER)
.then(response => {
dispatch(moviesIsLoading(false));
dispatch(moviesDataSuccess(response.data));
})
.catch(e => {
console.log(e);
dispatch(moviesHasErrored(true))
});
};
}
export function getMovieDetails(id) {
return (dispatch) => {
dispatch(moviesIsLoading(true));
API.get(URL_MOVIE_DETAILS(id))
.then(response => {
dispatch(moviesIsLoading(false));
dispatch(moviesDataSuccess(response.data));
})
.catch(e => {
console.log(e);
dispatch(moviesHasErrored(true))
});
};
}
Reducers:
export function movieHasErrored(state = false, action) {
switch (action.type) {
case MOVIES_ERROR:
return action.hasErrored;
default:
return state;
}
}
export function movieIsLoading(state = false, action) {
switch (action.type) {
case MOVIES_LOADING:
return action.isLoading;
default:
return state;
}
}
export function movieItems(state = {}, action) {
switch (action.type) {
case MOVIES_RESULTS:
return action.items;
default:
return state;
}
}
const rootReducer = combineReducers({
movieHasErrored,
movieIsLoading,
movieItems
});
I will be happy to all the recommendations. Thank you.

React router matches the closes URL first, I think the issue is the order of your component.
I suggest you order them like this and it should get resolved:
const Routes = () => {
return <div>
<Route path="/details/:id" component={MovieDetailsContainer} />
<Route exact path="/" component={Main} />
</div>;
}
Also React Router has a component for switching between URLs that you can take advantage if your URLs are ambiguous:
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
...
<Switch>
<Route path="/about" component={About} />
<Route path="/company" component={Company} />
<Route path="/:user" component={User} />
</Switch>
PS: Sorry I just wanted to comment instead of posting this answer if this is not the answer you're looking for, I just don't yet have reputation to comment directly :|

My mistake was in other place.
The problem was, I was using the same action-type for list results and details reasult.
When I was opening a page for details results, I've got props from previous page with list results ('cause they use the same action-type). Before my Details Component would fetch new data.
And my code was failing in render with wrong data.

Related

Props gets updated 3 times in single refresh

I am new in react.I am trying to use react-redux style from the beginning.
Below is what I tried for a simple product listing page.
In my App.js for checking if the user is still logged in.
class App extends Component {
constructor(props) {
super(props);
this.state = {}
}
componentDidMount() {
if (isUserAuthenticated() === true) {
const token = window.localStorage.getItem('jwt');
if (token) {
agent.setToken(token);
}
this.props.appLoad(token ? token : null, this.props.history);
}
}
render() {
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
isUserAuthenticated() === true
? <Component {...props} />
: <Redirect to='/logout' />
)} />
)
return (
<React.Fragment>
<Router>
<Switch>
{routes.map((route, idx) =>
route.ispublic ?
<Route path={route.path} component={withLayout(route.component)} key={idx} />
:
<PrivateRoute path={route.path} component={withLayout(route.component)} key={idx} />
)}
</Switch>
</Router>
</React.Fragment>
);
}
}
export default withRouter(connect(mapStatetoProps, { appLoad })(App));
In my action.js appLoaded action is as under
export const appLoad = (token, history) => {
return {
type: APP_LOAD,
payload: { token, history }
}
}
reducer.js for it
import { APP_LOAD, APP_LOADED, APP_UNLOADED, VARIFICATION_FAILED } from './actionTypes';
const initialState = {
appName: 'Etsync',
token: null,
user: null,
is_logged_in: false
}
const checkLogin = (state = initialState, action) => {
switch (action.type) {
case APP_LOAD:
state = {
...state,
user: action.payload,
is_logged_in: false
}
break;
case APP_LOADED:
state = {
...state,
user: action.payload.user,
token: action.payload.user.token,
is_logged_in: true
}
break;
case APP_UNLOADED:
state = initialState
break;
case VARIFICATION_FAILED:
state = {
...state,
user: null,
}
break;
default:
state = { ...state };
break;
}
return state;
}
export default checkLogin;
And in Saga.js I have watched every appLoad action and performed the operation as under
import { takeEvery, fork, put, all, call } from 'redux-saga/effects';
import { APP_LOAD } from './actionTypes';
import { appLoaded, tokenVerificationFailed } from './actions';
import { unsetLoggeedInUser } from '../../helpers/authUtils';
import agent from '../agent';
function* checkLogin({ payload: { token, history } }) {
try {
let response = yield call(agent.Auth.current, token);
yield put(appLoaded(response));
} catch (error) {
if (error.message) {
unsetLoggeedInUser();
yield put(tokenVerificationFailed());
history.push('/login');
} else if (error.response.text === 'Unauthorized') {
unsetLoggeedInUser();
yield put(tokenVerificationFailed());
}
}
}
export function* watchUserLogin() {
yield takeEvery(APP_LOAD, checkLogin)
}
function* commonSaga() {
yield all([fork(watchUserLogin)]);
}
export default commonSaga;
After that for productLists page my code is as under
//importing part
class EcommerceProductEdit extends Component {
constructor(props) {
super(props);
this.state = {}
}
componentDidMount() {
**//seeing the props changes**
console.log(this.props);
this.props.activateAuthLayout();
if (this.props.user !== null && this.props.user.shop_id)
this.props.onLoad({
payload: Promise.all([
agent.Products.get(this.props.user),
])
});
}
render() {
return (
// JSX code removed for making code shorter
);
}
}
const mapStatetoProps = state => {
const { user, is_logged_in } = state.Common;
const { products } = state.Products.products.then(products => {
return products;
});
return { user, is_logged_in, products };
}
export default connect(mapStatetoProps, { activateAuthLayout, onLoad })(EcommerceProductEdit);
But in this page in componentDidMount if I log the props, I get it three time in the console. as under
Rest everything is working fine. I am just concerned,the code i am doing is not up to the mark.
Any kinds of insights are highly appreciated.
Thanks
It's because you have three state updates happening in ways that can't batch the render.
You first render with no data. You can see this in the first log. There is no user, and they are not logged in.
Then you get a user. You can see this in the second log. There is a user, but they are not logged in.
Then you log them in. You can see this in the third log. There is a user, and they are logged in.
If these are all being done in separate steps and update the Redux store each step you'll render in between each step. If you however got the user, and logged them in, and then stored them in the redux state in the same time frame you'd only render an additional time. Remember React and Redux are heavily Async libraries that try to use batching to make sure things done in the same time frame only cause one render, but sometimes you have multiple network steps that need to be processed at the same time. So no you're not doing anything wrong, you just have a lot of steps that can't easily be put into the same frame because they rely on some outside resource that has its own async fetch.

Trouble with saving data to state

I've got a useEffect that sets state.user, which allows me to persist state and keep a user logged in as they navigate around. Auth.currentSession and Auth.currentAuthenticatedUser (line 105-106) are part of the AWS Amplify library and basically pull from localStorage (which I just learned about yesterday). So useEffect runs, calls the dispatch on line 110. line 115 prints the returned data from Auth.currentAuthenticatedUser, but 116 prints something equivalent to initialState when I expect to see values equivalent to "user" because "user" is sent as payload with the dispatch to "LOGIN". I'm guessing the "[ ]" argument in the useEffect has something to do with it, but I can't figure out what value to put in there to keep it from going into an infinite loop. My main goal is to save data to state and use it in other components with useContext.
import React, { useEffect } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import './App.css';
import MainNavbar from './components/Navbar';
import Home from './components/Home';
import LogIn from './components/auth/LogIn';
// import Register from './components/auth/Register';
import ForgotPassword from './components/auth/ForgotPassword';
import ForgotPasswordVerification from './components/auth/ForgotPasswordVerification';
import ChangePassword from './components/auth/ChangePassword';
import ChangePasswordConfirm from './components/auth/ChangePasswordConfirm';
// import Welcome from './components/auth/Welcome';
// import Ingredients from './components/Ingredients';
import Recipes from './components/Recipes';
import Footer from './components/Footer';
import { Auth } from 'aws-amplify';
// import axios from 'axios';
import { library } from '#fortawesome/fontawesome-svg-core';
import { faCheckSquare, faCoffee, faEdit } from '#fortawesome/free-solid-svg-icons';
library.add( faCheckSquare, faCoffee, faEdit);
// const config = require('./config.json');
export const AuthContext = React.createContext();
const initialState = {
isAuthenticated: false,
// isAuthenticating: true,
user: null,
userSettings: null,
userRec: null,
userIng: null,
userData: null,
defaultIng: null
}
const reducer = (state, action) => {
switch (action.type) {
case "LOGIN":
console.log("LOGIN");
return {
...state,
isAuthenticated: true,
user: action.payload
};
case "LOGOUT":
console.log("LOGOUT");
return {
...state,
isAuthenticated: false,
user: null
};
case "INITIALIZE_USER_DATA":
const userId = state.user.attributes.sub;
console.log(state.user);
let userRec = [];
let userIng = [];
let userData = [];
let defaultIng = [];
async function initializeUserData() {
try {
const params = {
"userId": userId,
"sett": true
};
let res1 = await axios.patch(`${config.api.invokeUrl}/user/configsettings`, params);
let res2 = await axios.post(`${config.api.invokeUrl}/recipes/${userId}`, params);
let res3 = await axios.get(`${config.api.invokeUrl}/ingredients`);
console.log(res1);
console.log(res2);
console.log(res3);
defaultIng = res3.data.sort((a, b) => (a.iName > b.iName) ? 1 : -1);
// this.setUserSettings(res1.data.Attributes); //4-13-2020, Myles - seems to be the same data coming from res2 and assigned to userData.
let arr1 = res2.data;
arr1.forEach( item => {
if (item.sk.startsWith("USER-")) {
userData.push(item);
} else if (item.sk.startsWith("REC-")) {
userRec.push(item);
} else if (item.sk.startsWith("ING-")) {
userIng.push(item);
}
});
} catch (error) {
console.log(error);
}
}
initializeUserData();
return {
...state,
isAuthenticated: true,
user: action.payload,
userRec: userRec,
userIng: userIng,
userData: userData,
defaultIng: defaultIng
};
default:
return state;
}
};
function App() {
const [state, dispatch] = React.useReducer(reducer, initialState);
async function authStatus() {
try {
let session = await Auth.currentSession();
let user = await Auth.currentAuthenticatedUser();
console.log(user);
console.log(session);
if (session && user) {
dispatch({
type: "LOGIN",
payload: user
});
};
console.log(user);
console.log(state);
}catch(error) {
console.log(error);
}
};
useEffect(() => {
authStatus();
},[]);
return (
<AuthContext.Provider
value={{
state,
dispatch
}}
>
<div className="App">
<Router>
<div className="container">
<MainNavbar />
<Switch>
<Route exact path="/" component = {Home} />
<Route exact path="/login" render={() => <LogIn />} />
<Route exact path="/recipes" render={() => <Recipes />} />
<Route exact path="/forgotpassword" render={() => <ForgotPassword />} />
<Route exact path="/forgotpasswordverification" render={() => <ForgotPasswordVerification />} />
<Route exact path="/changepassword" render={() => <ChangePassword />} />
<Route exact path="/changepasswordconfirmation" render={() => <ChangePasswordConfirm />} />
</Switch>
<Footer />
</div>
</Router>
</div>
</AuthContext.Provider>
);
}
export default App;
I'm guessing the [] argument in the useEffect has something to do with it (state is always initialState), but I can't figure out what value to put in there to keep it from going into an infinite loop!!
A common question that taunts many of us when using the famous useEffect hook. Indeed, the useEffect has something to do with your state updates, if any side effects actually change your state.
Note: If you pass an empty array [], the props and state inside the effect will always have their initial values.
Quick solution: (+)
Either add state to the useEffect dependency array
or console.log(state) within the return() statement
// summarized version, to focus on issue at hand
function App() {
const [state, dispatch] = React.useReducer(reducer, initialState);
// invoked within the useEffect()
async function authStatus() {
try {
let session = await Auth.currentSession();
let user = await Auth.currentAuthenticatedUser();
if (session && user) { dispatch({ type: "LOGIN", payload: user }) };
console.log(user);
console.log(state); // thus, this is always initialState (see useEffect() below)
} catch(error) {
console.log(error);
}
};
useEffect(() => {
authStatus();
// If you pass an empty array ([]), the props and state
// inside the effect will always have their initial values.
- }, []);
// Either, refactor the dependency array to include "state"
+ }, [state]);
return (
<AuthContext.Provider value={{ state, dispatch }}>
<div className="App">
+ {console.log(state)} // Or, log "state" here
<Router>{/* Routes */}</Router>
</div>
</AuthContext.Provider>
);
}
export default App;
Here's a detailed explanation:
Credit to the React team who did a good job to explain the useEffect() and it's effects:)
Note that by default, useEffect runs both after the first render and after every update. Sometimes you don't want the useEffect to always run after render.How do I make that happen?
Thus to optimize performance, you can skip effects by passing an array [] as an optional second argument to useEffect. Again, the React team recommends using the exhaustive-deps rule as part of the eslint-plugin-react-hooks package. It warns when dependencies are specified incorrectly and suggests a fix. Note, this is included, by default, if you use create-react-app.
If you use this optimization (mentioned above), make sure the array includes all values from the component scope (such as props and state) that change over time and that are used by the effect. Otherwise, your code will reference stale values from previous renders. That's why if you console.log() here, props and state will always have their initial values.

retrieve job details by job id using redux and axios

I have a jobs list. I have to retrieve a job details by passing 'jobid' as a paramater using axios. i have created action and reducer for that and connect to that my component. api is getting called but in jobid it is showing undefined. I think it is route problem. Please suggest me where to defined that and how to get the details.
My api is like(ex: 'http://localhost:3000/app/jobs/87938'). Here jobid is 87938. There are multiple job with different job id in my job list. Problem is how to define the jobid and passing to the api to retrieve the details of job
action code:
export const retrieveLocations = (jobId) => (dispatch) => {
axios.get(retrieveUrl+'/jobs/'+jobId).then(res => {
dispatch({
type: RETRIEVE_LOCATION,
payload: res.data.job.basicDetails
});
});
};
reducer code:
case 'RETRIEVE_LOCATION':
return{
...state,
conLocations:action.payload
}
component code:
class ConfiguredLocation extends React.Component{
constructor(props){
super(props);
this.handleRemove = this.handleRemove.bind(this);
this.clearall = this.clearall.bind(this);
}
handleRemove(mruCode){
this.props.removeLocation(mruCode)
}
clearall (){
this.props.removeAllLocation()
}
componentDidUpdate(prevProps){
let currJobId = this.props.match.params.jobId;
let prevJobId = prevProps.match.params.jobId;
if(currJobId!==prevJobId){
this.props.retrieveLocations(jobId);
}
}
componentDidMount(){
let {jobId} = this.props.match.params;
this.props.retrieveLocations(jobId);
}
render(){
const _labels = store.getLabels();
const {conLocations} = this.props;
return(
<div className="col-padding">
<div className="pos-div"><h3>Configured Location</h3><button className="allLargeBtn" onClick={()=>{this.clearall()}}>Remove all location</button></div><hr/>
<table className="table">
<tbody>
{conLocations.map((loct,index)=><tr key={index}>
<td><h5>{loct.mruCode} - {_labels[loct.division]} - {loct.country}</h5></td>
<td className="text-right"><img alt="DeleteIcon" onClick={()=>this.handleRemove(loct.mruCode)}className="deleteIconStyle" src="img/delete_large_active.png" /></td>
</tr>
)}
</tbody>
</table>
</div>
);
}
}
const mapStateToProps = state =>{
return {
conLocations: state.locationRed.conLocations
};
};
const mapDispatchToProps = (dispatch) =>{
return{
removeLocation: (mruCode)=>{dispatch(removeLocation(mruCode))},
removeAllLocation: () =>{dispatch(removeAllLocation())},
retrieveLocations:(jobId) =>{dispatch(retrieveLocations(jobId))}
};
};
export default connect(mapStateToProps,mapDispatchToProps)(withRouter(ConfiguredLocation));
router code(appnavigator - i am not able to define job id here. Please suggest me on this)
import React from 'react';
import ReactDOM from 'react-dom';
import {Router, Route} from 'react-router-dom';
import { Security, ImplicitCallback, SecureRoute } from '#okta/okta-react';
import history from '../history';
import store from '../stores/store';
import ConfiguredLocation from '../components/location/ConfiguredLocation';
class AppNavigator extends React.Component {
constructor( props ) {
super( props );
this.state = {
loading: true
};
}
componentDidMount() {
var self = this;
setTimeout(() => {
self.setState({ loading: false });
}, 1000);
}
render() {
if (this.state.loading) {
return (
<div className="fix"><i className="fa fa-2x fa-circle fa-spin"></i>
<div>Loading</div>
</div>
)
} else {
return (
<Router history={history}>
<Security issuer={Something..}
client_id={something...}
redirect_uri={window.location.origin + '/app/callback'}
scope={['profile', 'openid']}>
<Route path='/callback' component={ImplicitCallback} />
<AppFrame />
</Security>
<Route exact path= '/jobs/:jobId' component={ConfiguredLocation}
</Router>
);
}
}
};
store code:(where appnavigator called)
<Provider store={createStoreWithMiddleware(reducers)}>
<AppNavigator />
</Provider>
Everything is working fine. If i call the api without parameter. it is working fine. So i am not able to route the parameter properly. Please help me on this.
l.png
All your code is correct after editing. When you want to generate dynamic route, like jobId in /app/jobs/87938 route, first you should write this route in router file like this:
<Route exact path="/app/jobs/:jobId" component={ConfiguredLocation}/>
after that, When open http://localhost:3000/app/jobs/87938 route your jobId is 87938 and you can get it in your componentDidMount lifecycle:
componentDidMount() {
const {jobId} = this.props.match.params;
this.props.retrieveLocations(jobId);
}
Demo:

Can't display data using combine reducer REACT

without redux it works so that not a api connection problem
I have an express app connected to react with proxy I have already managed to display my data in react but now i want to make that in redux soo:
There is my problem, i have maked all the reducers/action, store and combine reducer but I didn't see any datas in my page and i haven't any errors
There is my code :
Action
export const api = ext => `http://localhost:8080/${ext}`;
//
// ─── ACTION TYPES ───────────────────────────────────────────────────────────────
//
export const GET_ADVERTS = "GET_ADVERTS";
export const GET_ADVERT = "GET_ADVERT";
//
// ─── ACTION CREATORS ────────────────────────────────────────────────────────────
//
export function getAdverts() {
return dispatch => {
fetch("adverts")
.then(res => res.json())
.then(payload => {
dispatch({ type: GET_ADVERTS, payload });
});
};
}
export function getAdvert(id) {
return dispatch => {
fetch(`adverts/${id}`)
.then(res => res.json())
.then(payload => {
dispatch({ type: GET_ADVERT, payload });
});
};
}
reducer
import { combineReducers } from "redux";
import { GET_ADVERTS, GET_ADVERT } from "../actions/actions";
const INITIAL_STATE = {
adverts: [],
advert: {}
};
function todos(state = INITIAL_STATE, action) {
switch (action.type) {
case GET_ADVERTS:
return { ...state, adverts: action.payload };
case GET_ADVERT:
return { advert: action.payload };
default:
return state;
}
}
const todoApp = combineReducers({
todos
});
export default todoApp;
index.js
//imports
const store = createStore(todoApp, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById("app")
);
My advertlist page :
//imports..
class Adverts extends Component {
componentDidMount() {
this.props.getAdverts();
}
render() {
const { adverts = [] } = this.props;
return (
<div>
<Header />
<h1>Adverts</h1>
{adverts.map(advert => (
<li key={advert._id}>
<a href={"adverts/" + advert._id}>
{advert.name} {advert.surname}
</a>
</li>
))}
<Footer />
</div>
);
}
}
const mapStateToProps = state => ({
adverts: state.adverts
});
export default connect(
mapStateToProps,
{ getAdverts }
)(Adverts);
I think your problem is here:
function mapStateToProps(state) {
return {
**adverts: state.adverts**
};
}
It should work if you change state.adverts to state.todos.adverts:
function mapStateToProps(state) {
return {
adverts: state.todos.adverts
};
}
Because your reducer is called todos, and it has state { adverts }, that's why you cannot access adverts even tho they are obtained.
You can check out working version here: https://codesandbox.io/s/olqxm4mkpq
The problem is, when you just create a store with one reducer without using combine reducer, it is possible to refer it directly in the ContainerS, like this:
const mapStateToProps = state => {
return{
*name of var*: state.adverts /*direct refers to adverts*/
}
}
But, when it use combined-reducer , it has to refer to an exact reducer that you want to use.like this :
const mapStateToProps = state => {
return{
*name of var* : state.todos.adverts (indirect refers to adverts from combined-reducer todos)
}
}

Redux-form empties itself when clicking on a Link component which loads the same form

I have a profile page containing a redux-form ProfileForm for which I set some initialValues. In my page header, I have a react-router Link to the /profile route.
The first time the page loads, the form is initialized correctly. However, if I click on the Link element, the form empties itself. I would have expected the form to keep its values maintained by the redux-form state (or at least to be initialized to initialValues).
What am I doing wrong? Is there a workaround?
Note: I am using react 16, react-router 4 and redux-form 7. I am also using redux-thunk in my action generators when fetching data.
Code
Profile.js
The component Profile waits for the initialValues to be set before rendering the ProfileForm for the first time. It shows a Loading component until the data is fetched.
//...
import {
fetchData,
submitData,
} from '../../actions';
class Profile extends Component{
componentDidMount() {
this.props.fetchData();
}
render(){
if(!this.props.initialValues){
return <Loading />
}else{
return <ProfileForm initialValues={this.props.initialValues} />
}
}
}
class ProfileForm extends Component{
onSubmit(values){
return this.props.submitData(values);
}
render(){
const { handleSubmit } = this.props;
return (
<div>
<Form
onSubmit={handleSubmit(this.onSubmit.bind(this))}
className="container">
<Field
name="first_name"
type="text"
title="First name"
component={SingleInput} />
...
<Button type="submit"
Sumbit
</Button>
</Form>
</div>
)
}
}
// validate, warn, etc.
// ...
function mapStateToProps(state) {
return {
initialValues: state.profile.data // set by the profile reducer upon fetching the data
};
}
export default connect(mapStateToProps,{ fetchData })(Profile);
ProfileForm = reduxForm({
form: 'ProfileForm',
fields: ['first_name', ...],
enableReinitialize: true,
validate,
warn,
})(
connect(mapStateToProps, { submitData })(ProfileForm)
);
App.js
//src/components/App.js
render() {
return (
<div className="App">
<Header />
<Main />
</div>
);
}
Header.js
The Header component contains, among other things, a Link component pointing to /profile.
//src/components/header.js
render(){
return (
...
<Link className="nav-link" to="/profile">Profile</Link>
...
)
}
Main.js
I have a profile page that is accessible thanks to React-Router v4 under /profile
//src/components/main.js
render(){
return (
<Switch>
<Route path='/profile' component={Profile}/>
...
</Switch>
)
}
EDIT: actions generator and reducers
I am using axios to fetch and submit data and redux-thunk to dispatch callbacks once I receive the data.
Action generators
//src/actions/index.js
export function fetchData(){
return (dispatch) => {
axios.get(`${FETCH_URL}`)
.then(response => {
dispatch({
type: FETCH_DATA_SUCCESS,
payload: response.data
});
})
.catch(error => {
dispatch({
type: FETCH_DATA_FAILED,
payload: error
})
})
}
}
export function submitData(values){
return (dispatch) => {
return axios.post(`${SUBMIT_URL}`,values)
.then(response => {
dispatch({
type: SUBMIT_DATA_SUCCESS,
payload: values,
});
})
.catch(error => {
dispatch({
type: SUBMIT_DATA_FAILED,
payload: error
});
})
};
}
Reducers
//src/reducers/profile.js
export default function(state={}, action) {
switch(action.type) {
case FETCH_DATA_SUCCESS:
return { ...state, profile: action.payload };
case FETCH_DATA_FAILED:
// Note that I never reach this
return { ...state, profile: {} };
case SUBMIT_DATA_SUCCESS:
return { ...state, profile: action.payload };
case SUBMIT_DATA_FAILED:
return { ...state };
}
return state;
}
Your problem is that the form is unmounted/mounted for whatever reason, I glanced your code and my first thought is the Loading component. When it renders, the Form is not. What you can do is on the following:
Hide the form instead of removing it (pass a isVisible or something to the form) or let redux-form keep the state for you when the form is unmounted. You can do this by setting the prop destroyOnUnmount=false to the reduxForm hoc.

Categories