Passing data from same level components React - reactjs

I have a HealthForm component where the user enters an url in a text input and clicks a button to submit. I have that url saved as a state in the component and I call some APIs which are all working.
The problem is that I have several other components that need that url and I can't seem to find a way to pass it to them.
My App.js looks like this which is why all other posts/tutorials are confusing me.
class App extends React.Component {
render(){
return(
<MuiThemeProvider>
<Router>
<div className="App">
<Route path="/" component={()=>(
<div>
<Header/>
<HealthForm/>
</div>)}/>
<Route path="/path1" component={ProductForm}></Route>
<Route path="/path2" component={xForm}></Route>
<Route path="/path3" component={yForm}></Route>
<Route path="/path4" component={zForm}></Route>
</div>
</Router>
</MuiThemeProvider>
);
}
}
HealthForm
class HealthForm extends React.Component {
constructor(props) {
super(props);
this.state = {
jarvisURL: '',
jarvisURLError: '',
status: '',
showStatus: false
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
validate = () => {
//…checks for input errors
}
handleChange(event) {
this.setState({
[event.target.name]: event.target.value
});
}
handleSubmit(event) {
event.preventDefault();
const err = this.validate();
let that = this;
if (!err) {
this.setState({
jarvisURLError: ''
});
console.log(this.state);
var data = this.state.jarvisURL
//… a fetch API call
}
}
render() {
return (
<form>
<TextField
...
/>
<br/>
<Button variant="contained" size="small" color="primary" onClick={e => this.handleSubmit(e)} >
Check
</Button>
<br /> <br />
...
</form>
);
}
}
export default HealthForm;
EDIT
Decided to do the suggestion from Grim
However the saga continues here with another problem: react page refreshes

It is correct that horizontally flowing data is frowned upon, but the reality is that in complex applications its often unavoidable. This is exactly why React provides the Context API. You start by creating a context for the Jarvis data:
import React from "react";
export const JarvisContext = React.createContext();
export class JarvisProvider extends React.Component {
constructor(props) {
super(props);
this.state = {
url: null,
setUrl: this.setUrl
};
}
render() {
return (
<JarvisContext.Provider value={this.state}>
{this.props.children}
</JarvisContext.Provider>
);
}
setUrl = (url) => {
this.setState({url});
}
}
Then anytime you need to access the Jarvis state (either setting or getting it) you use the Context.Consumer which passes the current state (which can include setter methods).
First you need to wrap any code that might use the JarvisContext.Consumer in the JarvisProvider. Note that you can also pass props to the provider to set the initial state. I often do this globally in the App.js component, but you can put it anywhere you need it.
<JarvisProvider>
<HealthForm />
</JarvisProvider>
Then consume it in the HealthForm render method:
render() {
return (
<JarvisContext.Consumer>
{({url, setUrl}) => {
<form>
<input type="text" value={url} onChange={(e) => setUrl(e.target.value)} />
</form>
}}
</JarvisContext.Consumer>
);
}
Note that this is not an exact copy of your functionality, but should be a good starting point. Saludos!

So there are a couple ways of doing this. The recommended method would be either the Context API or Redux. However there are some times where this can't be used in an Enterprise environment (I've worked places where Redux is used as an API cache and not used to store data set within the application). Another solution, albeit not best practices, is event bubbling.
With event bubbling you can pass a function as a property to the child component which then bubbles up to the parent component. This is done for several components such as the MaterialUI Button where you pass in a onClick listener. Using your handleClick function you would have something like:
handleSubmit(event) {
event.preventDefault();
const err = this.validate();
let that = this;
if (!err) {
this.setState({
jarvisURLError: ''
});
console.log(this.state);
var data = this.state.jarvisURL
//… a fetch API call
this.props.jarvisURLUpdated(this.state.jarvisURL);
}
}
Where jarvisURLUpdated is a function that calls the parent function and you can set the state of the parent as needed. The parent can then pass in the URL to the desired children.
Another option is the usage of local storage or cookies to store the URL in the browser and reuse (also not recommended by convention). This tends to work a bit better than event bubbling if you're using something like react-router-dom and displaying the information on separate pages (Redux would eliminate the event bubbling issue due to a global state).

Either use ContextAPI or any state management API like redux.

Related

React Component with multiple child components

I am learning React, but the tutorials I have followed mostly stick with using a single component or multiple different components. Moving from these tutorials to build my React application, I realized that I can't find a good example on inheritance. I did read this from the documentation which seems to focus more on adding functional components, which is different from what I am doing. My question here, is for guidance to check if my approach isn't something that will shoot me in the foot later or there is a better way.
I am using a traditional class based component structure. My parent component follows below. This code block connects to my node server to render a basic form that is prefilled with data from the server. The submitHandler then updates the server.
class StandardComponent extends Component {
constructor(props) {
super(props);
this.submitHandler = this.submitHandler.bind(this)
}
// Connect to the server
callBackendApi = table_name => {
return new Promise(async (resolve, reject) => {
const response = await fetch(`./api/list_all?table=${table_name}`)
const body = await response.json()
if (response.status !== 200) {
reject(body.errorMessage);
} else {
resolve(body.message)
}
})
}
// Check component mounted
async componentDidMount() {
const table_headers = this.state.table_headers
const data = table_headers.map(table_name => {
return this.callBackendApi(table_name)
.catch(err => console.log(err))
})
// Resolve promises and update state
Promise.all(data).then(results => this.setState({data: results, initial_loading_screen: false}))
}
async submitHandler(event) {
const table_headers = this.state.table_headers
const table_columns = GetColumnNames(this.state.data)
const raw_page_data = GetPageData(event, table_columns)
await SendPageData(raw_page_data, table_headers, table_columns, '/api')
event.preventDefault();
}
render() {
return <InitialRender />
}
}
Here is a snippet of one of my classes that inherit my parent class. I use this class to render my page.
class Content extends StandardComponent {
constructor(props) {
super(props);
this.state = {
// API request
data: null,
table_headers: ['resume_header', 'resume_skills', 'resume_experience', 'resume_education'],
// Check loading state
initial_loading_screen: true,
hasPageLoaded: null,
// Errors
hasError: false,
}
this.submitHandler = this.submitHandler.bind(this)
}
render() {
if (this.state.initial_loading_screen) return <InitialRender />
return (
<form onSubmit={this.submitHandler}>
<EditorView
data={this.state.data}
section_headers={['', '', 'Job', 'Certification']}
section_titles={['Title Block', 'Skills Block', 'Work Experience', 'Education Block']}
/>
<div className='button-group'>
<button type='submit' className='btn btn-outline-primary active'>Submit</button>
<button type='submit' name='render' className='btn btn-outline-primary'>Render</button>
<button type='reset' className='btn btn-outline-primary'>Cancel</button>
<button type='button' className='btn btn-outline-primary'>Editor</button>
<div className='button-group__view'>
<button type='button' onClick={() => window.open('/resume/render')} className='btn btn-primary'>View</button>
</div>
</div>
</form>
)
}
Now I am not posting this as a code review. Tips and pointers would be appreciated, but not necessary. In my inherited class, I initialize my state values and customize my render. For this example class, I am rendering a resume form; and in other classes, I am rendering a similar form but will have different state values and renders.
My question then, is if the approach I am using in the correct direction, or due to my inexperience with React (and web development in general), a more robust method that I am unaware of?
I'm unfamiliar with class components, but I'd recommend learning functional components. Class components are slowly being faded out. I can't answer your question but just wanna prevent you from having to relearn something a different way.
Don't inherit, use combination instead
You can read the guide from react docs

Dispatching Redux actions on location change with react-router-dom

I am using React and Redux for a search application. Using react-router-dom, I'm routing /search/:term? to a Search component:
<Router>
<Switch>
<Route exact path="/search/:term?" component={Search} />
<Redirect to="/search" />
</Switch>
const Search = (props) => {
const { term } = props.match.params;
return (
<div>
<SearchForm term={term}/>
<SearchResults />
</div>
)
};
When a user submits a search in the SearchForm component, I'm dispatching an action to submit the search query. I'm also initiating a search in the constructor if a term is given, initially:
class SearchForm extends Component {
constructor(props) {
super(props);
const term = props.term ? props.term : '';
this.state = {
term: term,
}
if (term) {
this.props.submitSearch(term);
}
}
handleSubmit = (e) => {
e.preventDefault();
if (this.state.term) {
this.props.submitSearch(this.state.term);
}
}
render = () => {
<form
onSubmit={this.handleSubmit.bind(this)}>
...
</form>
}
}
I'm using withRouter from react-router-dom, so the URL updates when the search is submitted.
The problem happens when the user navigates Back in their browser. The URL navigates back, the props update (i.e. props.match.params.term), but the search does not resubmit. This is because the submitSearch action only gets dispatched in SearchForm.constructor (search on initial loading if a term is in the URL) and SearchForm.handleSubmit.
What is the best way to listen for a state change to term when the URL changes, then dispatch the search action?
I would retrieve the route parameter in componentDidMount since you are pushing a new route and therefore reloading the view.
Inside your SearchForm it would look like this.
state = {
term: '';
}
onChange = (term) => this.setState({ term })
onSubmit = () => this.props.history.push(`/search/${this.state.term}`);
And in your SearchResult :
componentDidMount() {
this.props.fetchResults(this.props.term)
}
A nice thing to do would be to keep the SearchResult component dry. There are several ways to achieve that, here is one using higher order components aka HOC :
export default FetchResultsHoc(Component) => {
#connect(state => ({ results: state.searchResults }))
class FetchResults extends React.Component {
componentDidMount(){
dispatch(fetchResults(this.props.match.params.term))
}
render(){
<Component {...this.props} />
}
}
return FetchResultsHoc;
}
That you would then call on your SearchResult component using a decorator.
import { fetchResults } from './FetchResultsHoc';
#fetchResults
export default class SearchResult extends React.PureComponent { ... }
// You have now access to this.props.results inside your class
My current solution is to dispatch submitSearch in the componentWillRecieveProps lifecycle method if the new props don't match the current props:
componentWillReceiveProps(nextProps) {
if (this.props.term !== nextProps.term) {
this.setState({
term: nextProps.term,
});
this.props.submitSearch(nextProps.term);
}
}
Then, instead of dispatching an action on form submission, I push a new location onto the history and componentWillReceiveProps does the dispatching:
handleSubmit = (e) => {
e.preventDefault();
if (this.state.term) {
this.props.history.push('/search/'+this.state.term);
}
}
This solution feels a little wrong, but it works. (Other's would seem to agree: Evil things you do with redux — dispatch in updating lifecycle methods)
Am I violating a React or Redux principle by doing this? Can I do better?

Where do I call setState for redux values?

I'm pretty new to react native and async programming, and trying to understand how to "sync" redux state values and local state values.
For example, I have a text field "aboutMe" stored server side, and using mapStateToProps to place it into props:
const mapStateToProps = (state) => {
return { aboutMe: state.aboutMe };
}
In render, I have a TextInput I'm using so that the user can edit this field, and I would like to default to what is saved on the server side:
<TextInput
onChangeText={(aboutMe) => {
this.setState({aboutMe});
}}
value={this.state.aboutMe}
/>
Basically, somewhere I need to call
this.setState({ aboutMe: this.props.aboutMe });
Where is the right place to this? I was trying to use componentWillReceiveProps, but that lifecycle method is not called on constructor, so I would need to setState twice (in constructor and in componentWillReceiveProps).
Is there another way to do this? I feel like this is a pretty generic problem that a lot of react native developers have solved but I couldn't find a generally accepted way online.
Thanks!
Edit:
I have alot of TextInputs, so I have a separate button to call the action to save the variables:
<Button onPress={()=>{
this.props.saveUserInput(this.state.aboutMe,
this.state.name, this.state.address, ....}}>
<Text> Save changes </Text>
</Button>
From the comments, I understand that it's possible to call the save action onChangeText... but is that too much traffic back and forth? Would it be better to save all of the variables locally to state and then call a save for everything at once? Also, what if the user would like to "cancel" instead of save? The changes would have been already saved and we will not be able to discard changes?
1) If your component is a controlled component (you need state in it) and the request is asynchronous indeed you have to set the state in the componentWillReceiveProps like this:
class ExampleComp extends Component {
constructor(props) {
super(props);
this.state = {
aboutMe: ""
}
}
componentWillReceiveProps(nextProps) {
this.setState({
aboutMe: nextProps.aboutMe,
});
}
render() {
return (
<TextInput
onChangeText={(aboutMe) => {
this.setState({aboutMe});
}}
value={this.state.aboutMe}
/>
);
}
}
Keep in mind the key here is that the state must remain the single source of truth from now on.
2) The other option would be, you can wait until the request is finished in the parent component and then set the aboutMe in your constructor, this way you can avoid componentWillReceiveProps. For example:
class ParentComp extends Component {
render() {
return (
<div>
{this.props.aboutMe && <ExampleComp/>}
</div>
);
}
}
class ExampleComp extends Component {
constructor(props) {
super(props);
this.state = {
aboutMe: props.aboutMe
}
}
render() {
return (
<TextInput
onChangeText={(aboutMe) => {
this.setState({aboutMe});
}}
value={this.state.aboutMe}
/>
);
}
}
The downside of this is that the text input won't be shown until the request is finished.
Since you have edited your question, it is more clear what you want to achieve, so I want to address that.
You could keep the state of your controlled input elements in the component, then use the redux store to store persistent data and to populate the default values.
class Component extends React.Component {
constructor(props) {
super(props)
this.state = {
aboutMe: props.aboutMe,
... // other data
}
}
handleSubmit = (event) => {
event.preventDefault() // To prevent redirect
// Dispatch the save user input action
this.props.dispatch(saveUserInput(this.state))
}
render() {
return (
<form onSubmit={this.handleSubmit} />
<TextInput onTextChange={text => this.setState({...this.state, aboutMe: text}) />
... // input fields for other data
// Clicking this fill trigger the submit event for the form
<button type="submit">Save</button>
</form>
)
}
}

Handling responded forms in react

I'm doing a simple project that has something like 3 forms and right now I start the component with empty Inputs and then request data from API to pre-populate the form using the componentWillMount() hook.
It works for me now but if someday my app need more and more data it would be annoying to do this everytime for any new form and I would like to know if there is any lib or pattern to help pre-populating forms without using any state container (Redux, mobx, and I really don't know if they are needed in this case).
It is better to do your data fetching in componentDidMount than in componentWillMount:
If you need to load data from a remote endpoint, this is a good place
to instantiate the network request.
If you want to reuse some data fetching logic without any external state you could use Component with render props or Higher Order Components.
For example:
function withData(fetchData) {
return BaseComponent => {
class WithData extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
}
}
componentDidMount(){
fetchData().then(response => {
this.setState({ data: response })
})
}
render(){
return <BaseComponent {...this.props} data={this.state.data} />
}
}
return WithData;
}
}
And later you can reuse this logic:
const DataList = ({ data }) => (
<ul>
{
data.map(item =>
<li>{item.name}</li>
)
}
</ul>
)
// passing Promises as a `data` producers
const UserDataList = withData(fetchUsers)(DataList);
const GroupDataList = withData(fetchGroups)(DataList);
const CatsDataList = withData(() => fetchAnimals('cats'))(DataList);
const ListOfEverything = () => (
<Container>
<UserDataList />
<GroupDataList />
<CatsDataList />
</Container>
)

When is the render() called in a class that extends Component?

What refreshes the view in react or is the code always live displayed?
I have a function called removeAdmin and makeAdmin which adds and removes users as Admins and then when a user is an admin the render of Member component renders and admin shield logo. It works fine but I'm wondering whether render is being triggered each time I change the UI using a function or if render is live listening to changes in it's components?
class MemberList extends Component {
constructor(props) {
super(props)
this.state = {
members: [],
loading: false,
administrators: []
}
this.makeAdmin = this.makeAdmin.bind(this)
this.removeAdmin = this.removeAdmin.bind(this)
}
componentDidMount(){
this.setState({loading:true})
fetch('https://api.randomuser.me/?nat=US&results=12')
.then(response => response.json())
.then(json => json.results)
.then(members => this.setState({
members,
loading:false
}))
}
makeAdmin(email){
const administrators = [
...this.state.administrators,
email
]
this.setState({administrators})
}
removeAdmin(email){
const administrators = this.state.administrators.filter(
adminEmail => adminEmail !== email
)
this.setState({administrators})
}
render() {
const { members, loading } = this.state
return (
<div className="member-list">
<h1>Society Members</h1>
{
(loading) ?
<span> loading...</span>:
<span>{members.length} members</span>
}
{ (members.length)?
members.map(
(member, i) =>
<Member key={i}
// This admin prop is worked out by enumerating through the administrator
// array with some(). some() passes in the enumerators, checking whether
// the current member in members.map() exists in the administrators array
// and returns admin=true if so.
admin={this.state.administrators.some(
adminEmail => adminEmail === member.email
)}
name={member.name.first + '' + member.name.last}
email={member.email}
thumbnail={member.picture.thumbnail}
makeAdmin={this.makeAdmin}
removeAdmin={this.removeAdmin}/>
):
<span>Currently 0 members</span>
}
</div>
)
and the Member component:
class Member extends Component {
componentWillMount(){
this.style={
backgroundColor: 'grey'
}
}
render() {
const { name, thumbnail, email, admin, makeAdmin, removeAdmin } = this.props
return (
<div className="member" style={this.style}>
<h1>{ name } {(admin) ? <FaShield/> : null}</h1>
<div>
<img src={ thumbnail }/>
</div>
<div>
{
(admin)?
<Button title="Make Admin" onClick={() => removeAdmin(email) } color="#841584"> Remove Admin </Button>
:
<Button title="Make Admin" onClick={ () => makeAdmin(email) } color="#841584"> Make Admin </Button>
}
<a href={`mailto:${ email }`}><p> {email} </p></a>
</div>
</div>
)
}
}
export default Member
What triggers a new render on components is when the state changes or when receiving new properties.
There are two main objects that drive the render in each component, the this.props and the this.state. If any of this objects gets updated then the render method gets executed.
The this.props object gets updated whenever you send new properties to the childrens. this.state gets updated using the this.setState method.
That being said, it's really important to keep track of the properties you send to the children, as a rule of thumb I always recommend not using the spread operator to pass props to the children, for example:
<Parent>
<Child {...this.props} />
</Parent>
I'd avoid that pattern because if any of the props changes, than all props are sent to the child. Instead I recommend sending only what the children needs.
<Parent>
<Child some={this.props.value} />
</Parent>
You need to be very careful when you need to render your component, otherwise it's so easy to re-render everything! Which will lead to performance issues.
It depends on what you define your component render method to be.
It can change based on the state or props that you give it.
Less frequent, but you can check out shouldComponentUpdate as it allows you to overwrite the method to give it more “smarts” if you need the performance boost.

Resources