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.
Related
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
My case requires that I use React.createRef() for each time picker I have (there is a certain functionality in the time pickers that can be only used through Refs).
When the page is mounted, I have initially 2 time pickers. All works well when I have only two, but my work is ruined because I am required to Add/Remove Time Pickers using buttons. So I am able to add the time pickers easily.
But now my question is how do I create the refs ? My declaration for React.createRef() is in the Constructor(){} for the first 2 refs.
The question is where do I instantiate the refs for the time pickers that are added onClick ?
Thank you
You should wrap your time picker in an another component, create the ref there and perform the work that requires a ref inside of that components then forwarding the result via props (you can pass a function via a prop).
For example, you could give each component an unique ID (uuid does a great job of that), pass that via a prop, pass a value and pass a function that accepts an ID and a value, then call that function whenever a result from the ref is obtained.
You could do something like this, but it requires you to have a unique identifier per component that should not be the index. (Cause this can change)
Pseudo Code
class Wrapper extends Component {
construct() {
...
this.refsById = {}
}
getRefOrCreate(id) {
if(_has(this.refsById[id]) {
return this.refsById[id];
} else {
this.refsById[id] = React.createRef();
return this.refsById[id];
}
}
onClickHandler(value, id) {
const ref = this.refs[id];
const { onClick } = this.props;
}
render(){
// Here you need to know how many pickers you need, and their id
const { pickersInformationArray} = this.props;
return (
<div> { pickersInformationArray.map((id) => <TimePicker ref={this.getRefOrCreate(id);} onClick={(value) => { this.onClickHandler(value, id); } } )} </div>
)
}
I found the solution.
Let me first say that I was using the method of creating ref in the constructor in my incorrect solution
class DummyClass extends Component {
constructor(){
this.timePickerRef = React.createRef();
}
}
render() {
let timers = array.map( index => {
<TimePicker
ref={timepicker => timePickerRef = timepicker}
value={00}
onChange={(data) =>
{this.handleTimePcikerValueChange(data, index);}}
/>
}
return (
timers
)
}
}
what I instead did was the following
disregard and remove this.timePickerRef = React.createRef() since it will no longer be necessary
while the code in handleTimePcikerValueChange & render will be as follows:
handleTimePcikerValueChange = (value, index) => {
// do whatever manipulation you need
// and access the ref using the following
this[`timePicker_${index}`]
}
render() {
let timers = array.map( index => {
<TimePicker
ref={timepicker => this[`timePicker_${index}`] = timepicker}
value={00}
onChange={(data) =>
{this.handleTimePcikerValueChange(data, index);}}
/>
}
return (
timers
)
}
I didn't post the code that handle adding time pickers because it is irrelevant.
I would like to thank those who responded !
tell me, please, how to solve the following problem correctly?
I have a certain component, there is a control above, when I click on it, setState is triggered. I need to call the function this.setScrollLeft () in which I set to the selected node (ref) in this case the cleavage position.
Here is my implementation, but I am sure that there is a better solution:
import React from 'react';
import { ScoreCell, getScoreTheme } from 'components/scores';
class LeaderboardPlayerResult extends React.Component {
constructor(props) {
super(props);
this.containerWidth = 198;
this.data = this.props.data;
this.playerResultRef = React.createRef();
}
componentDidMount() {
this.element = this.playerResultRef.current;
this.element.scrollLeft = this.containerWidth;
}
setScrollLeft = () => {
if (this.element) {
this.element.scrollLeft = this.containerWidth;
}
};
playerResult = () => {
if (this.data.playOffHoles) {
return (
this.data.playOffHoles.map((item, index) => {
return (
<div
className="leaderboard__player-result-row-wrapper"
key={index}
>
<div className="leaderboard__player-result-row">
<div className="leaderboard__player-result-cell">{item.holeId}</div>
</div>
<div className="leaderboard__player-result-row">
<div className="leaderboard__player-result-cell">{item.holePar}</div>
</div>
<div className="leaderboard__player-result-row">
<div className="leaderboard__player-result-cell leaderboard__player-result-cell--score">
<ScoreCell
childCss='tee-times-card__score'
theme={getScoreTheme(item.playOffParScore)}
>{item.playOffParScore}</ScoreCell>
</div>
</div>
</div>
);
})
);
}
};
render() {
console.log('LeaderboardPlayerResult render');
this.setScrollLeft();
return (
<div
className="leaderboard__player-result"
ref={this.playerResultRef}
>
{this.playerResult()}
</div>
);
}
}
The best place to put this.setScrollLeft() is inside the componentDidUpdate method.
You are already calling this method (this.setScrollLeft()) inside componentDidMount, what is right. Now, you could put another call into componentDidUpdate and it will work pretty much as it is working by now because componentDidUpdate is called before render.
The final outcome will be the same, however, you are separating the concerns: render only render the components and the other methods deal with your business logic.
If you are not sure about componentDidMount and componentDidUpdate, see these excerpts from the official React.js documentation:
componentDidMount()
componentDidMount() is invoked immediately after a component is mounted. Initialization that requires DOM nodes should go here. If you need to load data from a remote endpoint, this is a good place to instantiate the network request. Setting state in this method will trigger a re-rendering.
componentDidUpdate()
componentDidUpdate() is invoked immediately after updating occurs. This method is not called for the initial render.
I get data from and API async and, I thought, solved the related timing issues here, but I still have issues with the render not updating.
Since my component would not update I broke it down to a basic stateless version and then it started working. I've tried to make identical versions of the components, +/- state. Only the stateless version works as I said, but I need the full component to work so I can do stuff with the state. Otherwise I'd use it as is, stateless.
(Side note: Not sure if this is 'React-ful', but I use only props in the example in the full React.Component, never setting anything to state. Moving on...)
Full version component - unworking fiddle
class Posts extends React.Component {
constructor(props) {
super(props)
this.state = {};
this.posts = props.data;
}
render() {
return this.posts.map((post, index) => {
return (
<div className="comment" key={index}>
<div className="by">{post.by}</div>
<div className="id">{post.id}</div>
</div>
)
})
}
}
stateless functional - working fiddle
function Posts(props) {
let posts = props.data;
return posts.map((post, index) => {
return (
<div className="comment" key={index}>
<div className="by">{post.by}</div>
<div className="id"> {post.id}</div>
</div>
)
})
}
For some reason the full component does not re-render on click while the stateless does. I thought I resolved the timing issues but perhaps not? Is the component type truly causing the thing to break/work? Why?
I tried to make the only difference btw the fiddles the way Posts is structured, still the fiddles are quite busy. Hopefully though more effective in showing my problem than showing the code here.
Cause component will re-render when props or state update. In your state full component constructor only invoke once. So this.post not updated. You can fix that in 2 way
Change this.posts when props component will change
componentWillReceiveProps (nextProps) {
this.posts = nextProps.posts
}
Or use directly props.post in render (this better)
return this.props.posts.map((post, index) => {...})
class Posts extends React.Component {
constructor(props) {
super(props)
this.state = {};
}
render() {
return this.props.data.map((post, index) => {
return (
<div className="comment" key={index}>
<div className="by">{post.by}</div>
<div className="id">{post.id}</div>
</div>
)
})
}
}
First of all try something like this instead of initializing the posts in constructor. In your case it might me trying to render before initializing this.posts
Looking at this simple example where the prop toggleData would be a redux thunk action mapped to the container props.
Is this the recommended way to pass a function like this to a child 'dumb' component? I read an article online saying that using arrow functions inside handlers is expensive and not very efficient from a performance perspective.
class Container extends React.Component {
render(){
return (
<Home toggleData={this.props.toggleData}/>
)
}
}
const Home = (props) => {
return (
<button onClick={()=>{props.toggleData()}}></button>
)
}
Yes! Avoid using arrow functions inside the JSX. Because the function will be created on every render, this might lead to performance issues later on.
If you don't need to send parameters you can do something like this:
const Home = (props) => {
return (
<button onClick={props.toggleData}></button>
)
}
If you need to use parameters, I usually define a class to create the callback using an arrow function, this way it gets created and bound only once.
class Home extends PureComponent {
onToggle = () => {
this.props.toggleData(1, 2, 3);
}
render() {
return (
<button onClick={this.onToggle}></button>
)
}
}