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
Related
My app has multiple Popover components, I know how to handle the state of one Popover component, using something like this:
class App extends Component {
constructor(props) {
super(props);
this.state = { pop_open: false };
}
handleProfileDropDown(e) {
e.preventDefault();
this.setState({
pop_open: !this.state.pop_open,
anchorEl: e.currentTarget,
});
}
handleRequestClose() {
this.setState({
pop_open: false,
});
};
render() {
return (
<div>
<button type="submit" onClick={this.handleProfileDropDown.bind(this)} >My Customized PopOver</button>
<Popover
open={this.state.pop_open}
anchorEl={this.state.anchorEl}
onRequestClose={this.handleRequestClose.bind(this)}
>
{"content"}
</Popover>
</div>
);
}
}
But for more than one Popover, I do not know how to do that, should I create a state for each Popover? Sorry for the question but I am new to the frontend world.
note: kindly do not use hooks in your answer.
An internal state is a good option when only the Component is going to modify it. It keeps the logic simple and inside the same block of code. On the other hand managing the state from outside of the Component lets other components read its values and modify them. This is a common approach when using Redux or Context, where there is a global app state. This state is meant for properties that several Components need to read/write to.
Which to use when is a design decision and depends on each situation. In my opinion each Component should handle its own state when possible. For example, when values are only going to be modified by it, or a children Component. Having an external state makes sense when multiple Components are going to read or modify it, or when the state values need to be passed several levels deep in the hierarchy.
In the example you propose I can see that the Popover is working with an internal state. This can work and you can use the Component several times and it will carry all the logic inside. If you rename the Components you can see more easily what I mean. I dont know exactly how the Component with the button works but this is to make the explanation clear:
class Popover extends Component {
constructor(props) {
super(props);
this.state = { is_open: false };
}
open = () => {
this.setState({
is_open: true
});
}
close = () => {
this.setState({
is_open: false
});
}
toggle = () => {
this.setState(prevState => ({
is_open: !prevState.is_open
}));
}
render() {
return (
<div>
<button onClick={this.toggle}>
Open
</button>
{this.state.is_open && <PopoverContent />}
</div>
);
}
}
If you need further explanation or something is not clear, let me know.
Im new in ReactJS...
I have a project with the following class components structure:
index.js
--app
--chat
--header
--left
--right
In the chat.js component, I make a google search with the api to retrieve images based on specific keyword... My intuitive solution was:
this.client.search("cars")
.then(images => {
for(let el of images) {
ReactDOM.render(<img src="{{el.url}}" syle="{{width: '100%'}}" />, document.querySelector('#gimages'));
}
});
It is correct? Or I may to use Components with stored states with flux (redux)?
Perhaps a simpler more conventional use of react would achieve what your require?
You could follow a pattern similar to that shown below to achieve what you require in a more "react-like" way:
class Chat extends React.Component {
constructor(props) {
super(props)
this.state = { images : [] } // Set the inital state and state
// model of YourComponent
}
componentDidMount() {
// Assume "client" has been setup already, in your component
this.client.search("cars")
.then(images => {
// When a search query returns images, store those in the
// YourComponent state. This will trigger react to re-render
// the component
this.setState({ images : images })
});
}
render() {
const { images } = this.state
// Render images out based on current state (ie either empty list,
// no images, or populated list to show images)
return (<div>
{
images.map(image => {
return <img src={image.url} style="width:100%" />
})
}
</div>)
}
}
Note that this is not a complete code sample, and will require you to "fill in the gaps" with what ever else you have in your current Chat component (ie setting up this.client)
This is not the way you should go, you don't need to use ReactDOM.render for each item. Actually, you don't need to use ReactDOM.render at all. In your component you can use a life-cycle method to fetch your data, then set it to your local state. After getting data you can pass this to an individual component or directly render in your render method.
class Chat extends React.Component {
state = {
images: [],
}
componentDidMount() {
this.client.search( "cars" )
.then( images => this.setState( { images } ) );
}
renderImages = () =>
this.state.images.map( image => <Image key={image.id} image={image} /> );
render() {
return (
<div>{this.renderImages()}</div>
);
}
}
const Image = props => (
<div>
<img src={props.image.url} syle="{{width: '100%'}}" />
</div>
);
At this point, you don't need Redux or anything else. But, if you need to open your state a lot of components, you can consider it. Also, get being accustomed to using methods like map, filter instead of for loops.
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.
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>
)
I am new with React(way) of building UIs and also dabbling with FP a little bit.
Basically , I wanted to know if using curried Functions to delay binding events with fields is a good practice and are there any performance implications.
For EG:
I have a set of Posts and for Each Post there is a Comment Box, where users can comment on that POST.So we will need the Post and the relevent Comment for that Post in my Event Hanlder which is just a function.
Check the working code example at CodePen :
Event Binding
/*
* A simple React component
*/
class Application extends React.Component {
constructor(props){
super(props)
console.log(props)
this.state = {
posts:['post1', 'post2', 'post3']
}
}
render() {
return (
<div>
<ul>
{ this.state.posts.map(
(post, i) => {
return (
<PostSingle post = {post} index = {i} bindPost = {this.props.post(post)}/>
)
}
) }
</ul>
</div>
)
}
}
const PostSingle = ({post, index,bindPost}) => {
let handlePostComment = () => {
return bindPost(null)
}
return (
<div>
<li key={index}>
<input onChange = {
(e) => {
let value = e.target.value
handlePostComment = () => {
return bindPost(value)
}
}
}></input>
<button onClick={() => {handlePostComment()}}>Comment</button>
<p key={index}>{post}</p>
</li>
</div>
)
}
/*
* Render the above component into the div#app
*/
const PostComment = post => comment => {
alert(`You commented ${comment} for ${post}`)
}
ReactDOM.render(<Application post={PostComment}/>, document.getElementById('app'));
So basically PostComment function gets properties in a curried fashion as an when the Objects gets created.
i could not find much examples of these except in Redux Tutorials.
In Actual application the events can be passed to the main component using props in mapDispatchToProps() using Redux.
Your thoughts and comments would be much appreciated.
I think using post property and state is a much more Reacty way. handlePostComment will be reinitialized on every render call so this code is more imperative then functional (IMHO using closures does not make the code functional).
State is the React way of handling imperative logic and you can benefit from React optimizations by using state and props properly.
Generally I think it breaks React Redux philosophy of having a single source of truth.
Also you can't make your input controlled by providing a value in this case.