React router problem fetching data when needed - javascript

I have a tricky situation with react router 4.
Imagine I have a route
<Route path='/search/:term?' render={(props) => {
return (<ProductList
setFlag={(flag)=>this.setState({flag})}
{...props}
/>)
}} />
Now you can see I am using render in Route which means it will not unmount this component at each render rather update the old instance with new props.
However, at some point inside the ProductList the user calls setFlag function which you can see updates some property in parent.
Because of this, a rerender of the parent is caused. Which also calls componentWillReceiveProps(CWRP) of ProductList. Inside CWRP of ProductList I am always (unconditionally) fetching items with new props.
This causes my problem. You can see that when user updated flag, there was no need to fetch data again in CWRP, because updating that flag wasn't related to my data.
You could say that I should put some condition in CWRP that would do some check and fetch data only when it is necessary. However, I find it impossible to come up with such check. Because for example, ProductList receives a search term. I could, for example, compare a search term from the previous render to search term of new render and if they are different then to fetch data, however, that is incorrect, because even in case of same search term a fetch should be issued (maybe the data was updated on a server).
What solution do you see in such a situation?
So that my product list doesn't fetch data everytime the flag of parent changes?

Elevate your state and move your logic out of the render method and into a parent container-component, then utilize this.setState() to stop state updates OR use shouldComponentUpdate() to continue to allow state updates BUT stop re-renders when the flag hasn't been changed (either one will prevent ProductList from being updated):
import React, { Component } from 'react';
import ProductList from './ProductList';
export default class SearchTerms extends Component {
state = { flag: '' };
shouldComponentUpdate = (nextProps, nextState) => ( this.state.flag !== nextState.flag )
handleFlag = flag => this.setState(prevState => { return this.state.flag !== flag ? { flag } : null })
render = () => ( <ProductList setFlag={this.handleFlag} {...this.state} {...this.props} /> )
}
Then the route will change to:
<Route path='/search/:term?' component={SearchTerms} />
In addition, I'd avoid using componentWillReceiveProps() altogether and instead use componentDidUpdate().
Here's an example of a parent container-component controlling several component children. The children can update the parent via a passed down parent method.
In this simple example, searchForPlayer's onChange and onSubmit updates the parent's searchTerm state and changes the URL query via parent's handleSubmit method. The URL query change triggers the parent's componentDidUpdate method, which then fetches new data and updates the displayPlayerList component.
URL before:
/players/all
URL after form submit:
/players/player?number=${this.state.searchTerm}
So if a user types out the URL to:
/players/player?number=10
or
/players/fdskmsdfk?number=10
and hits enter, it'll load a filtered list because it's only looking for a number query.
If they go to:
/player/dsfdsdfdsdf
or
player/1223345
or anything without a number query, then it'll just fetch all the players instead (this can be handled differently, but was done for simplicity).
Working example: https://codesandbox.io/s/xn3p3o6vq
containers/PlayersList.js (parent container-component)
import isEmpty from "lodash/isEmpty";
import React, { Component, Fragment } from "react";
import qs from "qs";
import DisplayPlayerList from "../components/displayPlayerList";
import NoPlayerFound from "../components/noPlayerFound";
import SearchForPlayer from "../components/searchForPlayer";
import ServerError from "../components/serverError";
import Spinner from "../components/spinner";
export default class PlayersList extends Component {
state = {
err: "",
isLoading: true,
searchTerm: "",
players: [],
noplayer: "",
number: ""
};
componentDidMount = () => this.fetchPlayers();
componentDidUpdate = (prevProps, prevState) => this.props.location.search !== prevProps.location.search && this.fetchPlayers();
fetchPlayers = () => {
const { number } = qs.parse(this.props.location.search, { ignoreQueryPrefix: true })
fetch(`https://jsonplaceholder.typicode.com/users${number ? `/${number}` : ""}`)
.then(response => response.json())
.then(players =>
this.setState({
err: "",
players: !number ? [...players] : [players],
noplayer: isEmpty(players) ? true : false,
isLoading: false,
number,
searchTerm: ""
})
)
.catch(err => this.setState({ err: err.toString() }));
};
handleChange = e => this.setState({ searchTerm: e.target.value });
handleSubmit = e => {
e.preventDefault();
this.props.history.push(`/players/player?number=${this.state.searchTerm}`);
};
render = () => (
this.state.isLoading // (if isLoading is true..)
? <Spinner /> // (then show a spinner)
: <div style={{ padding: 20, width: 500 }}> // (otherwise...)
<SearchForPlayer // (show player search form and...)
handleChange={this.handleChange}
handleSubmit={this.handleSubmit}
{...this.state}
/>
{ this.state.err // (if there's an error...)
? <ServerError {...this.state} /> // (show the error)
: this.state.noplayer // (otherwise, if there's no player...)
? <NoPlayerFound {...this.state} /> // (show no player found)
: <DisplayPlayerList {...this.state} /> // (otherwise, display updated list)
}
</div>
);
}
components/searchForPlayer.js (child component)
import React from "react";
export default ({ handleChange, handleSubmit, searchTerm }) => (
<form onSubmit={handleSubmit}>
<input
className="uk-input"
type="number"
value={searchTerm}
onChange={handleChange}
placeholder="Search for player by number..."
style={{ width: 300, marginRight: 10 }}
min={1}
/>
<button
disabled={!searchTerm}
className="uk-button uk-button-primary"
type="submit"
>
Search
</button>
</form>
);
components/displayPlayerList.js (child component)
import map from "lodash/map";
import React from "react";
export default ({ players }) => (
<ul style={{ listStyleType: "none" }}>
{map(players, ({ id, name, username, email }) => (
<li style={{ margin: "10px 0" }} key={id}>
<strong>Player # {id}</strong>
<span> - {name}</span>
</li>
))}
</ul>
);
components/noPlayerFound.js (child component)
import React from "react";
export default ({ number }) => (
<div style={{ color: "red", padding: 20 }}>
No player was found matching #{number}!
</div>
);
component/serverError.js (child component)
import React from "react";
export default ({ err }) => (
<div style={{ color: "red", padding: 20 }}>
<i style={{ marginRight: 5 }} className="fas fa-exclamation-circle" /> {err}
</div>
);

Related

React native wrapper component re-renders on each page navigation

I am developing an android app using react-native and i have a but of a struggle of figuring out why my component re-renders on each page navigation.
In my case i have created a wrapper component called Container.android.js
const Container = props => {
useEffect(() => {
// fetching some data from async storage
},[])
// Using my hook here
const {code, error} = usePdaScan({
onEvent: (code) => {
// setting state via useState
},
onError: (error) => {
Alert.alert('Error', error);
},
trigger: 'always'
});
return <View>
{props.children}
</View>
}
Then i declare routes with Stack.Screen where each stack uses a component wrapped with the Container component.
<Stack.Screen name="Overview" component={Overview} />
And my Overview compnent is this
const Overview = props => {
return <Container>
<Text>Overview page</Text>
</Container>
}
My problem is that inside the Container component, there is a hook called usePdaScan. Each time i navigate to another page and the hook gets called, the Container component re-renders twice... I cant get a lead on this... Halp!
UPDATE: My homepage is a class component where it seems to work ok (render only once)
class Home extends Component {
state = {
productBarcodes: null
}
componentDidMount(){
// get data fetches data from async storage
getData('#productBarcodes').then(res => {
this.setState({
...this.state,
productBarcodes: JSON.parse(res)
})
})
}
render() {
const { productBarcodes } = this.state;
return (
<Container {...this.props}>
<View style={{marginBottom: 20, flex:1}}>
{productBarcodes.length} Products
</View>
</Container>
)
}
}
The usePdaScan hook in the Container component gets called once on my Homepage but twice on every other page like Overview etc... Both Homepage and Overview are wrapped with my Container component

Send searchParam data from one Component to another component in reactjs

I have one Component which shows a list of data in a dropdown and there is an option to search these data which works as a filter. Here is my code:
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import Popover from '../../Popover';
import Input from '../../Input';
import Icon from '../../Icon';
import IconButton from '../../IconButton';
const DropDownFilter = props => {
const { label, options, onChange, isSearchEnabled } = props;
const [activeOption, setActiveOption] = useState({});
const [filter, setfilter] = useState('');
const searchFilter = event => {
setfilter(event.target.value);
};
const removeFilter = () => {
setfilter('');
};
const lowercasedFilter = filter.toLowerCase();
const filteredData = options.filter(item => {
return Object.keys(item).some(
key => typeof item[key] === 'string' && item[key].toLowerCase().includes(lowercasedFilter)
);
});
const labelText = activeOption.label ? activeOption.label : label;
const handleSelectedOption = option => {
setActiveOption(option);
onChange(option);
};
return (
<div className="filter">
<Popover linkText={labelText} size="small" direction="bottom-left">
{isSearchEnabled && (
<div className="filter__search">
<Input
value={filter}
onChange={searchFilter}
preIcon={
<div role="presentation">
<Icon name="search" />
</div>
}
placeholder="Search"
postIcon={
filter.length > 0 && (
<IconButton
icon={<Icon name="close" />}
size="tiny"
onClick={removeFilter}
standalone={true}
isIconOnly={true}
/>
)
}
/>
</div>
)}
<ul className="filter__options filter__options--scrollbar">
{filteredData.map(option => (
<li
key={option.value}
role="presentation"
className={classNames('filter__options-option', {
'filter__options-option--active': option.value === activeOption.value,
})}
onClick={() => handleSelectedOption(option)}
>
{option.label}
</li>
))}
</ul>
</Popover>
</div>
);
};
DropDownFilter.defaultProps = {
label: 'Filter Menu',
options: [],
isSearchEnabled: true,
};
DropDownFilter.propTypes = {
label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
options: PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
})
),
onChange: PropTypes.func.isRequired,
isSearchEnabled: PropTypes.bool,
};
export default DropDownFilter;
Here is a gif of it: https://recordit.co/HtalUtuPsj
Now during searching I want to send the value of the search param to another component, the value will be used to search from a DB or any other external data source which is being handled in that new component. Such as, if I am searching for Ratings, this component should search for it in the existing options list it has in its own component, as well as the same time it will search for Ratings in any other external data source or DB. This external network call, search or any other functionality will be processed in the other component. So this component will only send the search param; for example Ratings to the other component in real time.
I can think of an idea like I will get the searchParam in a state and pass the setState value to a new props which will be called through an onSearchParamChange function, this new function will pass the data through a callback and the other component will get the data through calling that props of this component. I am not sure if this is the correct way and also I am not able to implement this thought in the code either. Is there any better way to do it? if so what would be that coding implementation?
If you need to pass to a parent component you should be able to use for example the onChange prop which is passed to your component, like you are doing in the handleSelectedOption function. That function is in fact passing the chosen option to the parent component. If you want to pass to the parent component when the user is typing, then you should call the onChange function also in searchFilter:
const searchFilter = event => {
const option = event.target.value);
setfilter(option);
onChange(option);
};
If you want to pass it to a child component, the you can just pass it as prop:
<ChildComponent filter={ filter } />

React - Add user to list of Favorites

I have a simple user list with several details from the following api: https://gorest.co.in/public-api/users, where I want to add a selected user to a list of favorites. I am working with react-router to navigate between pages. Is this possible with React or do I also need Redux?
I have a complete LIVE EXAMPLE here with the user page and favorites.
Here is the code below for the user list:
import React from "react";
import axios from "axios";
import NavLinks from "./components/navLink";
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [],
addToFav: false
};
this.list = [];
}
componentDidMount() {
this.getList();
}
/* get users list */
getList = async () => {
const api =
"https://gorest.co.in/public-api/users?_format=json&access-token=3qIi1MDfD-GXqOSwEHHLH73Y3UitdaFKyVm_";
await axios
.get(api)
.then(response => {
this.list = response.data.result;
this.setState({
list: this.list
});
})
.catch(err => {
console.log(err);
});
};
addToFav = () => {
this.setState(
{
addToFav: !this.state.addToFav
},
() => console.log(this.state.addToFav)
);
};
render() {
let style = {
display: "grid",
gridTemplateColumns: "repeat(auto-fill, minmax(250px, 1fr))",
padding: "1rem",
gridGap: "1rem 1rem"
};
return (
<div>
<NavLinks />
<ul style={style}>
{this.state.list.map(user => {
return (
<li key={user.id}>
<div>
<img className="thumb" alt="" src={user._links.avatar.href} />
</div>
<div className="userInfo">
<p>
{user.first_name} {user.last_name}
</p>
</div>
<button onClick={this.addToFav}>Add to Favorites</button>
</li>
);
})}
</ul>
</div>
);
}
}
Thank you!
Here's a working codesandbox: https://codesandbox.io/s/brave-fire-4kd4p
This train of thought pretty much follows what #Chris G mentioned. Have a top-level state that holds the list of users and the favorites list. Then pass those as props to the individual components.
App.js
Hit your API here instead of inside your UserList component to prevent any unnecessary re-renders.
import React, { Component } from "react";
import UserList from "./userList";
import FavoriteList from "./favoriteList";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import axios from "axios";
export default class App extends Component {
state = {
list: [],
favorites: []
};
addFavorite = favorite => {
const { favorites } = this.state;
if (!favorites.some(alreadyFavorite => alreadyFavorite.id == favorite.id)) {
this.setState({
favorites: [...this.state.favorites, favorite]
});
}
};
getList = async () => {
const api =
"https://gorest.co.in/public-api/users?_format=json&access-token=3qIi1MDfD-GXqOSwEHHLH73Y3UitdaFKyVm_";
await axios
.get(api)
.then(response => {
this.setState({
list: response.data.result
});
})
.catch(err => {
console.log(err);
});
};
componentDidMount() {
this.getList();
}
render() {
return (
<Router>
<Switch>
<Route
path="/"
exact
render={() => (
<UserList list={this.state.list} addFavorite={this.addFavorite} />
)}
/>
<Route
path="/favorites"
render={() => <FavoriteList favorites={this.state.favorites} />}
/>
</Switch>
</Router>
);
}
}
UserList.js
Call the addFavorite event-handler on button-click to pass that item back up to the parent-state.
import React from "react";
import NavLinks from "./components/navLink";
export default class UserList extends React.Component {
render() {
let style = {
display: "grid",
gridTemplateColumns: "repeat(auto-fill, minmax(250px, 1fr))",
padding: "1rem",
gridGap: "1rem 1rem"
};
return (
<div>
<NavLinks />
<ul style={style}>
{this.props.list.map(user => {
return (
<li key={user.id}>
<div>
<img className="thumb" alt="" src={user._links.avatar.href} />
</div>
<div className="userInfo">
<p>
{user.first_name} {user.last_name}
</p>
</div>
<button onClick={() => this.props.addFavorite(user)}>
Add to Favorites
</button>
</li>
);
})}
</ul>
</div>
);
}
}
Favorite.js
Use the favorites array that was passed in as a prop and iterate over it.
import React from "react";
import NavLinks from "./components/navLink";
export default class FavoriteList extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
const { favorites } = this.props;
return (
<div>
<NavLinks />
<ul>
{favorites.map(user => {
return (
<li key={user.id}>
<div>
<img className="thumb" alt="" src={user._links.avatar.href} />
</div>
<div className="userInfo">
<p>
{user.first_name} {user.last_name}
</p>
</div>
</li>
);
})}
</ul>
</div>
);
}
}
Is this possible with React or do I also need Redux?
Most if not all of those problems can be solved without redux just by using component state. It just gets increasingly difficult to pass the state to the components needing it the more global state you have and the more components at different depth need to access and update it.
In your case it might be sufficient to store the favorites in a component state high up the tree and pass it to the components consuming it. You could either pass it directly to the components or you could use react context to make it accessible to components deep in the tree.
A simple example:
const FavoritesContext = React.createContext({favorites: []});
const FavoritesProvider = ({children}) => {
const [favorites, setFavorites] = useState([]);
const add = useCallback(favorite => setFavorites(current => [...current, favorite]), [setFavorites]);
return (
<FavoritesContext.Provider value={{favorites, add}}>
{children}
</FavoritesContext.Provider>
};
You can use it like that:
<FavoritesProvider>
<MyApp />
</FavoritesProvider>
then anywhere in a component in your app:
const MyComponent = () => {
const {favorites, add} = useContext(FavoritesContext);
const [draft, setDraft] = useState('');
const handleChange = event => setDraft(event.target.value);
const handleAdd = () => {
add(draft);
setDraft('');
};
return (
<div>
<ul>
{favorites.map(favorite => <li>{favorite}</li>)}
</ul>
<input value={draft} type="text" onChange={handleChange} />
<button onClick={handleAdd}>Add</button>
</div>
);
}
In this simple example the favorites are just text but they could as well be objects. Also it demonstrates how you could provide a handler for adding a favorite. You could implement e.g. a handler for removing favorites in the same way.
Persisting your favorites is yet another topic you may need to deal with. You could use e.g. localStorage for that or you could store that in a database on a server and fetch it when your app mounts the first time.
I have changed your file a bit take a look - https://codesandbox.io/s/clever-butterfly-vb2iz
One way is to use the localstorage of browser.
But this way is slighty expensive and synchronous.
Update the list whenever the favorited item status is changed via
localStorage.setItem('users',JSON.stringify(users));
And look for the favorited items via
localStorage.getItem('users');//You need to parse this by JSON.parse()
Maintain a isFavorite variable in the object list.
let users=[{name:"Mr.A",isFavorite:false},{name:"Mr.B",isFavorite:true},...];
On the click of favoriting button this.addToFav change it as follows
addToFav=user=>{
const {users}=this.state;
this.setState({
users:users.map(userObject=>userObject.id===user.id?
{...userObject,isFavorite:!userObject.isFavorite}:user)
},()=>{saveToLocal(this.state.users)});
}
Now you can access the favorite items even if the page is reloaded and stays there till you clear the storage.Use this localStorage.clear() for that.
First I would change your onClick to this:
<button onClick={() => this.addToFav(user.id)}>Add to Favorites</button>
This will allow you to pass the id to the addToFave function.
Then I would add a new state called faves (an array) and every time someone clicks the add button I would add their id into this array. This will allow you to filter your original list when you want to display the faves.
this.state = {
list: [],
faves: [],
};
}
addToFav = (id) => {
this.setState(prevState => ({
faves: [...prevState.faves, id],
}));
};
When I want to use the list of faves instead of the normal list I would do this:
const favesList = [];
this.state.list.map(listItem =>
this.state.faves.find(
faveId => listItem.id === faveId
) && favesList.push(item);
Then I would pass that to the faves component
I changed accordingly, please try
https://codesandbox.io/s/youthful-poincare-7oeh0
the key is you can use push state to your link like below
<Link to={{ pathname: "/favorites", state: { favList: this.props.favList }}} onClick={() => this.forceUpdate()}>
later on under your fav page call to retrieve the state
this.props.location.state.favList
i have changed the code a little by using react context.
I would not use redux for this cause i think it would be a overkill.
Anyways here is the updated sandbox...
Link for sandbox

Component is somehow rendering despite conditional statement

On my Home component, on initial load, I loop through an object of URLs, and use Promise.all to make sure they all resolve at the same time. I construct an object, and push it into state. At the start of this I have loading: true, then set to false when done pushing that object into state:
Home Component:
class Home extends Component {
state = {
searchTerm: null,
movies: {
trending: {},
topRated: {},
nowPlaying: {},
upcoming: {}
},
loading: false
};
componentDidMount() {
this.getInitalMovies();
}
getInitalMovies = () => {
const API_KEY = process.env.REACT_APP_API_KEY;
//set loading to true
this.setState({ loading: true });
//create an object with all movie URLs
const allMovieURLs = {
trending: `https://api.themoviedb.org/3/trending/movie/day?api_key=${API_KEY}`,
topRated: `https://api.themoviedb.org/3/movie/top_rated?api_key=${API_KEY}&language=en-US&page=1`,
nowPlaying: `https://api.themoviedb.org/3/movie/now_playing?api_key=${API_KEY}&language=en-US&page=1`,
upcoming: `https://api.themoviedb.org/3/movie/upcoming?api_key=${API_KEY}&language=en-US&page=1`
};
//break down the movieURL object into entries, fetch the URL, and reassign entries with actual data
//encapsulate within a Promise.all to ensure they all resolve at the same time.
const moviePromises = Promise.all(
Object.entries(allMovieURLs).map(entry => {
const [key, url] = entry;
return fetch(url).then(res => res.json().then(data => [key, data]));
})
);
//with the returned promise from Promise.all, reconstruct the array of entries back into an object with relevant key pair values
const movies = moviePromises.then(movieArr => {
const dataObj = {};
for (const [movie, movieData] of movieArr) {
dataObj[movie] = movieData;
}
return dataObj;
});
//with the returned object, push it into current state, then turn off loading
movies.then(movieObj =>
this.setState({ movies: movieObj, loading: false })
);
};
render() {
const { movies } = this.state;
return (
<div className='App'>
<Header
submitHandler={this.submitHandler}
changeHandler={this.changeHandler}
/>
<HeroImage />
{this.state.loading ? <Loader /> : <MovieDisplay movies={movies} />}
</div>
);
}
MovieDisplay Component:
export default class MovieDisplay extends Component {
render() {
const { movies } = this.props;
return (
<div>
<MovieRow movies={movies.trending.results} movieType='trending' />
<MovieRow movies={movies.topRated.results} movieType='top rated' />
<MovieRow movies={movies.nowPlaying.results} movieType='now playing' />
<MovieRow movies={movies.upcoming.results} movieType='upcoming' />
</div>
);
}
}
MovieRow Component:
export default class MovieRow extends Component {
render() {
const { movieType, movies } = this.props;
return (
<div>
<div className='row-title'>{movieType}</div>
{console.log(movies)} //This part seems to somehow mount even when conditional rendering says it shouldn't!
<Slider {...settings} />
</div>
);
}
}
I then have the body do a conditional render like so, so that if the loading is complete (loading: false), then my MovieDisplay component should render, otherwise it's still loading, so show the Loader component.
I confimed that this part is working (if I search the React Devtools for Loader when loading: false it does not exist, but MovieDisplay does exist.
I'm passing down a data object via props from Home > MovieDisplay > MovieRow, and then looping through the array to display more components.
However, on initial load, it seems the MovieRow (last, nested child component) is somehow being mounted for a quick second, because in the console it's logging 4 undefined statements briefly, before resolving with the proper data.
Main question: If the Parent Component is not rendered to the DOM, then the child components inside of the Parent should also not be rendered, right?
Secondary question: Is it possible that all the components in my app are rendering briefly for a second on initial load, despite having a conditional in the render() function? That's the only thing I can think of that's causing this.
Example: If MovieDisplay is not rendered, then everything inside of it like MovieRow should also not be rendered, correct?
Hope this isn't too confusing...please let me know if I need to edit my problem or elaborate.
.then does not resolve a promise. It lets you get the value after the promise was resolved
This is because of the asynchronous nature of JS. Initially, when componentDidMount is called, you set loading = true.
Before the promise is completed(loading = true) react renders the HOME component, this is the reason it calls MovieDisplay component.
Try adding an extra condition where you call MovieDisplay
{this.state.loading && "check your data is filled in movies object" ? <Loader /> : <MovieDisplay movies={movies} />}
Can you try this:
{this.state.loading && <Loader />}
{!this.state.loading && <MovieDisplay movies={movies} />}

Programmatically open a route with state in react

I have two types of item, one of which can contain data similar to the other.
Currently when form is used to save an item it saves it then uses browserHistory.push to show the next page.
But I wish add a button that will
save the currently item
redirect them to the form to add the other item type,
partially fill out this form with the data from the first item.
Is there a way to do this using react and not using local storage or session variables?
You should take a look to Redux (or other Flux based libraries) to store data between components and routes, avoiding the excessive prop nesting.
browserHistory.push won't work. It only moves you to a certain location but it doesn't update the application state. You need to update application state, which then will reflect into location update, but not in the opposite direction. Keep in mind that, in React, data comes first, and its representation, even though mutable, doesn't change the data back. The same applies to the location.
To make the redirect alone work, I'd recommend wrapping your component into withRouter higher-order component.
import React, { Component } from 'react';
import { withRouter } from 'react-router';
class MyComponent extends Component {
render() {
return (
<div>
<button
onClick={() => this.props.router.push('/new-location')}>
Click me to go to /new-location
</button>
</div>
);
}
}
But if you need to pass data from one component to another, and the two aren't in hierarchy, I'd agree with Alomsimoy and recommend using Redux. But if, for some reason, it's not an option, you can store this data in a component that is parent to both forms:
class FormA extends Component {
render() {
return (
<form onSubmit={() => this.props.onSubmit()}>
<input
type="text"
value={this.props.inputA}
onChange={(event) => this.props.handleChangeA(event)} />
</form>
);
}
}
class FormB extends Component {
render() {
return (
<form onSubmit={() => this.props.onSubmit()}>
<input
type="text"
value={this.props.inputB}
onChange={(event) => this.props.handleChangeB(event)} />
</form>
);
}
}
while their parent would rule the location and state updates:
class Forms extends Component {
constructor() {
super();
this.state = {};
}
handleChange(name, value) {
this.setState({
[name]: value
});
}
renderForm() {
const {
params: {
stepId
}
} = this.props;
if (stepId === 'step-a') { // <- will be returned for location /form/step-a
return (
<FormA
inputA={this.state.inputA}
handleChangeA={(event) => this.handleChange('inputA', event.target.value)}
onSubmit={() => this.props.router.push('/form/step-b')} />
);
} else if (stepId === 'step-b') { // <- will be returned for location /form/step-b
return (
<FormB
inputB={this.state.inputB}
handleChangeB={{(event) => this.handleChange('inputA', event.target.value)} />
);
}
}
render() {
const {
children
} = this.props;
console.log(this.state); // track changes
return (
<div>
{this.renderForm()}
<button
onClick={() => this.props.router.push('/new-location')}>
Click me to go to /new-location
</button>
</div>
);
}
}
export default withRouter(Forms);
so the route for them would look like
<Route path="form/:stepId" component={Forms} />

Categories

Resources