How to force remounting on React components? - javascript

Lets say I have a view component that has a conditional render:
render(){
if (this.state.employed) {
return (
<div>
<MyInput ref="job-title" name="job-title" />
</div>
);
} else {
return (
<div>
<MyInput ref="unemployment-reason" name="unemployment-reason" />
<MyInput ref="unemployment-duration" name="unemployment-duration" />
</div>
);
}
}
MyInput looks something like this:
class MyInput extends React.Component {
...
render(){
return (
<div>
<input name={this.props.name}
ref="input"
type="text"
value={this.props.value || null}
onBlur={this.handleBlur.bind(this)}
onChange={this.handleTyping.bind(this)} />
</div>
);
}
}
Lets say employed is true. Whenever I switch it to false and the other view renders, only unemployment-duration is re-initialized. Also unemployment-reason gets prefilled with the value from job-title (if a value was given before the condition changed).
If I change the markup in the second rendering routine to something like this:
render(){
if (this.state.employed) {
return (
<div>
<MyInput ref="job-title" name="job-title" />
</div>
);
} else {
return (
<div>
<span>Diff me!</span>
<MyInput ref="unemployment-reason" name="unemployment-reason" />
<MyInput ref="unemployment-duration" name="unemployment-duration" />
</div>
);
}
}
It seems like everything works fine. Looks like React just fails to diff 'job-title' and 'unemployment-reason'.
Please tell me what I'm doing wrong...

Change the key of the component.
<Component key="1" />
<Component key="2" />
Component will be unmounted and a new instance of Component will be mounted since the key has changed.
Documented on You Probably Don't Need Derived State:
When a key changes, React will create a new component instance rather than update the current one. Keys are usually used for dynamic lists but are also useful here.

What's probably happening is that React thinks that only one MyInput (unemployment-duration) is added between the renders. As such, the job-title never gets replaced with the unemployment-reason, which is also why the predefined values are swapped.
When React does the diff, it will determine which components are new and which are old based on their key property. If no such key is provided in the code, it will generate its own.
The reason why the last code snippet you provide works is because React essentially needs to change the hierarchy of all elements under the parent div and I believe that would trigger a re-render of all children (which is why it works). Had you added the span to the bottom instead of the top, the hierarchy of the preceding elements wouldn't change, and those element's wouldn't re-render (and the problem would persist).
Here's what the official React documentation says:
The situation gets more complicated when the children are shuffled around (as in search results) or if new components are added onto the front of the list (as in streams). In these cases where the identity and state of each child must be maintained across render passes, you can uniquely identify each child by assigning it a key.
When React reconciles the keyed children, it will ensure that any child with key will be reordered (instead of clobbered) or destroyed (instead of reused).
You should be able to fix this by providing a unique key element yourself to either the parent div or to all MyInput elements.
For example:
render(){
if (this.state.employed) {
return (
<div key="employed">
<MyInput ref="job-title" name="job-title" />
</div>
);
} else {
return (
<div key="notEmployed">
<MyInput ref="unemployment-reason" name="unemployment-reason" />
<MyInput ref="unemployment-duration" name="unemployment-duration" />
</div>
);
}
}
OR
render(){
if (this.state.employed) {
return (
<div>
<MyInput key="title" ref="job-title" name="job-title" />
</div>
);
} else {
return (
<div>
<MyInput key="reason" ref="unemployment-reason" name="unemployment-reason" />
<MyInput key="duration" ref="unemployment-duration" name="unemployment-duration" />
</div>
);
}
}
Now, when React does the diff, it will see that the divs are different and will re-render it including all of its' children (1st example). In the 2nd example, the diff will be a success on job-title and unemployment-reason since they now have different keys.
You can of course use any keys you want, as long as they are unique.
Update August 2017
For a better insight into how keys work in React, I strongly recommend reading my answer to Understanding unique keys in React.js.
Update November 2017
This update should've been posted a while ago, but using string literals in ref is now deprecated. For example ref="job-title" should now instead be ref={(el) => this.jobTitleRef = el} (for example). See my answer to Deprecation warning using this.refs for more info.

Use setState in your view to change employed property of state. This is example of React render engine.
someFunctionWhichChangeParamEmployed(isEmployed) {
this.setState({
employed: isEmployed
});
}
getInitialState() {
return {
employed: true
}
},
render(){
if (this.state.employed) {
return (
<div>
<MyInput ref="job-title" name="job-title" />
</div>
);
} else {
return (
<div>
<span>Diff me!</span>
<MyInput ref="unemployment-reason" name="unemployment-reason" />
<MyInput ref="unemployment-duration" name="unemployment-duration" />
</div>
);
}
}

I'm working on Crud for my app. This is how I did it Got Reactstrap as my dependency.
import React, { useState, setState } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import firebase from 'firebase';
// import { LifeCrud } from '../CRUD/Crud';
import { Row, Card, Col, Button } from 'reactstrap';
import InsuranceActionInput from '../CRUD/InsuranceActionInput';
const LifeActionCreate = () => {
let [newLifeActionLabel, setNewLifeActionLabel] = React.useState();
const onCreate = e => {
const db = firebase.firestore();
db.collection('actions').add({
label: newLifeActionLabel
});
alert('New Life Insurance Added');
setNewLifeActionLabel('');
};
return (
<Card style={{ padding: '15px' }}>
<form onSubmit={onCreate}>
<label>Name</label>
<input
value={newLifeActionLabel}
onChange={e => {
setNewLifeActionLabel(e.target.value);
}}
placeholder={'Name'}
/>
<Button onClick={onCreate}>Create</Button>
</form>
</Card>
);
};
Some React Hooks in there

Related

Manage many inputs between sibling components in React - best approach?

I am new to this topic.
In the parent component App I have two siblings : SideMenu and Document
The idea is that the user inputs values (SideMenu) which will be renedered on the Document. There will be more than 20 inputs. Since this is the first time I do this sort of state management, what are the best or maybe easiest approaches for this attempt of project.
function App() {
const [fullName, setFullName] = useState("")
const [address, setAddress] = useState("")
return (
<div className='app'>
<SideMenu />
<Document />
</div>
)
}
export default App
const SideBar = () => {
return (
<div>
<div className='input-group'>
<label>Full Name:</label>
<input type='text' />
</div>
<div className='input-group'>
<label>Address:</label>
<input type='text' />
</div>
</div>
)
}
const Document = () => {
return (
<div>
<h1>{fullName}</h1>
<p>{address}</p>
</div>
)
}
You can create an object for your form and store the form inputs in this object. Shared state can be stored in the most closest and common component (in your situation this is your parent component) according to your child components. [1]
When you make an update from a child component other child component that is sharing state will be syncronized and your state will be updated. You shouldn't use redux like state management tools unless you are need to set a global state.
I have made a revision for your example, this scenario allows you to pass the state in the parent component to the child components and update the state in the parent component from the child components.
I used a common event handler in the parent component, this functions captures the html event and we parse this event and update the state via this function. [2][3]
import "./styles.css";
import { useState } from "react";
import SideBar from "./SideBar";
import Document from "./Document";
export default function App() {
const [values, setValues] = useState({
fullName: "",
address: "",
postalCode: ""
});
function handleChange(event) {
setValues({ ...values, [event.target.name]: event.target.value });
}
return (
<div className="app">
<SideBar values={values} setValues={handleChange} />
<Document values={values} setValues={handleChange} />
</div>
);
}
export default function Document({ values }) {
return (
<div>
<h1>Document</h1>
<p>Full Name: {values.fullName}</p>
<p>Address: {values.address}</p>
<p>Postal Code: {values.postalCode}</p>
</div>
);
}
export default function Sidebar({ setValues }) {
return (
<div>
<div className="input-group">
<label>Full Name:</label>
<input type="text" name="fullName" onChange={setValues} />
</div>
<div className="input-group">
<label>Address:</label>
<input type="text" name="address" onChange={setValues} />
</div>
<div className="input-group">
<label>Address:</label>
<input type="text" name="postalCode" onChange={setValues} />
</div>
</div>
);
}
Code Sandbox Link: https://codesandbox.io/s/stackoverflow-74961591-wpmcnd
[1]: Passing Props to a component: https://beta.reactjs.org/learn/passing-props-to-a-component
[2]: Updating Objects in State: https://beta.reactjs.org/learn/updating-objects-in-state
[3]: HTML Change Event: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/change_event
The default, go-to solution would be to use a container component that controls your form inputs state (this would be App in your case). Just pass the values and setters down one level by props and everything should be ok, simple and predictable.
If things start to get complicated then libraries such as Formik or react-hook-form help a lot. When it comes to managing multiple or complex forms that may also need validation they are your best bet. I suggest you take this approach.
Using Redux for this kind of situation is a huge anti-pattern. Redux global store should be only used for global state, not local form state.
Context API is well suited when you need to pass data to multiple deeply nested children. This way you do not need to pass props dozens of levels down the tree. However, it is usually used by 3rd party libraries such as the ones mentioned above (all of them).
You can use Formik library for handling many inputs. Wrap both components inside Formik and use formik's methods.
import { Formik } from 'formik';
<Formik
initialValues={{ fullName: '', address: '' }}
onSubmit={(values) => {
alert(JSON.stringify(values, null, 2));
}}
>
{({handleChange, values, handleSubmit}) => (
<form onSubmit={handleSubmit}>
<div className='app'>
<SideMenu
handleChange={handleChange}
/>
<Document values={values} />
<button type="submit">Submit</button>
</form>
)}
</Formik>
You dont need to create multiple states for each input. handlChange will handle itself. You just need add name or id attribute to input. Also you can access values of each input using the values parameter like values.fullName.
const SideBar = ({handleChange}) => {
return (
<div>
<div className='input-group'>
<label>Full Name:</label>
<input
type='text'
onChange={handleChange}
name="fullName"
/>
</div>
<div className='input-group'>
<label>Address:</label>
<input
type='text'
onChange={handleChange}
name="address"
/>
</div>
</div>
)
}
const Document = ({values}) => {
return (
<div>
<h1>{values.fullName}</h1>
<p>{values.address}</p>
</div>
)
}

prevent re render component using React and React-memo

I would like to prevent component re-rendering using React. I've read some guides but I'm still having trouble getting my code to work.
The CreateItem component creates an input form from the json object. When the input states change, React re-renders all components. I would avoid this situation as it causes some problems.
I have used React.memo but my code still doesn't work. Is this a good way to implement this code? How can I correct my code? Thank you
function MyComponent() {
return(
<div className="row">
{Array.from(new Map(Object.entries(json))).map((data) => (
<CreateItem obj={data} />
))}
</div>
);
}
//function CreateDiv(props) {
const CreateDiv = React.memo((props) => {
console.log("rendering ");
return (
<form name="myForm" onSubmit= {formSubmit}>
<div className="row">
{Array.from(new Map(Object.entries(props.obj[1]))).map((data) => (
<>
{(() => {
return(
<div className="col-sm-2">
<CreateItem obj={data[1]} />
</div>
)
})()}
</>
))}
</div>
</form>
);
});
--- EDIT ---
CreateItem uses CreateCheckBoxComponent function to create my custom checkbox with default status from json value.
CreateCheckBoxComponent code is follwing:
function CreateCheckBoxComponent(props) {
if(parseInt(props.obj.defaultValue) === 5)
setChecked(false);
else
setChecked(true);
return(
<FormCheck
label={props.obj.simbolName}
name={props.obj.idVar}
type="checkbox"
checked={checked}
onChange={handleCheckBoxChange}
sm={10}
/>
);
}
HandleCheckBoxChange works fine and changes state, but when I click on checkbox to change the flag, CreateCheckBoxComponent is re-render and
it sets the default state again. I would like to avoid this problem and I think preventing re-rendering can be a solution..
React.memo only prevents own rerendering.
You have considered the following things.
If the children are using React.memo but the parent re-renders
the children will render also.
React.memo prevents re-rendering if the component's state changes. but if the prop changes, the component re-renders.
Note: make sure when you render elements/Components with the map function or any iteration always provide a unique key to them.
For more information click here

How to render two react list groups in the same container, chronologically

Basically, im looking to merge the two containers to be in one single container, with the most recent of either at the top.
So instead of
Most recent post
second recent post
third recent post
Most recent event
second recent event
third recent event
It is merged to be:
Most recent post
Second recent post
Most recent event
third recent post
econd recent event
third recent event
Heres what the code currently looks like:
render() {
return (
<div>
<MenuBar />
<div className="row">
<div className="col-lg-8 col-md-6 col-sm-12">
<PostContainer />
<EventContainer />
</div>
</div>
</div>
);
}
These "containers" are identical:
render(){
return (
<ListGroup>
{this.state.posts.map((post) => (
<PostView
key={post.id}
id={post.id}
title={post.title}
description={post.description}
region={REGION_CHOICES[post.region]}
datetimeCreated={post.datetime_created}
creator={post.creator}
likedUsers= {post.liked_users}
taggedUsers={post.tagged_users}
handleLike={this.handleLike}
/>
))}
</ListGroup>
)
}
}
Any ideas?
Thanks, everyone
Is it possible to change PostContainer and EventContainer into plain javascript classes?
Both could have functions which return a specified post/event Component. For posts, it could look something like this:
class Posts {
var postStore = [PostComponent1, PostComponent2, ...]
function getPost(index) {
return postStore[index];
}
}
Then, you can make the React Component a more generic class, Container, which does something like this:
import posts from './posts'
import events from './events'
class Container extends React.Component {
constructor(props) {
var postsAndEvents = [];
for (i = 0; i < 3; i++) {
postsAndEvents.push(posts.getPost(i))
postsAndEvents.push(events.getEvent(i))
}
}
render() {
return(
<div>
{postsAndEvents.map((Component) => {
return <Component />
})}
</div>
)
}
}
You can build the postsAndEvents array however you wish. The main idea is that you have to elevate the state (in this case, the state being the order of components). React Components do not return data to their owners.

How to give React parent component access to children state without form (with Sandbox)?

I'm currently looking for a way to access children state from a parent component that will handle API calls for the whole page.
The actual problem is the following:
Parent is the parent component that will render two Child components.
Each of the Child has a state that it is responsible for.
The "Kind of Submit Button" will have a "Kind of Submmit Action" (this is all quoted because this is not a form) and that should fire the function to provide access to the children state. Is there a way (some React feature) to do this without using <form> or without creating an intermediate parent component to hold all the state? I want each children to be responsible for its own state.
Code Sandbox with example of the code below
import React, { useState, useRef } from "react";
function ChildOne() {
const [childOneState, setChildOneState] = useState(false);
return (
<React.Fragment>
<h3>Child One</h3>
<p>My state is: {childOneState.toString()}</p>
<button onClick={() => setChildOneState(true)}>Change my state</button>
</React.Fragment>
);
}
function ChildTwo() {
const [childTwoState, setChildTwoState] = useState(false);
return (
<React.Fragment>
<h3>Child Two</h3>
<p>My state is: {childTwoState.toString()}</p>
<button onClick={() => setChildTwoState(true)}>Change my state</button>
</React.Fragment>
);
}
function Button(props) {
return (
<button onClick={props.kindOfSubmitAction}>Kind of Submit Button</button>
);
}
function Parent() {
const childOneState = useRef("i have no idea");
const childTwoState = useRef("ihave no idea");
function kindOfSubmitAction() {
console.log("This is the kindOfSubmit function!");
// This function would somehow get
// access to the children state and store them into the refs
return;
}
return (
<React.Fragment>
<h1>Iam Parent</h1>
<div>
<b>Child one state is: </b>
{childOneState.current}
</div>
<div>
<b>Child two state is: </b>
{childTwoState.current}{" "}
</div>
<Button kindOfSubmitAction={kindOfSubmitAction} />
<ChildOne />
<ChildTwo />
</React.Fragment>
);
}
export default Parent;
When several components need access to the same data, it's time for Lifting State Up.

React doesn't re-render on props change

I am kinda new to react and to the webitself.
this is my render function
render() {
const {repositories} = this.props
return (
<div className='mt4 bt b--black-20 boardingbox scrollarea-content' style={{overflow: 'scroll', height: '100vh'}}>
{
repositories.map((repo, index) => {
console.log(repo.name)
return <Note name={repo.name} desc={repo.name} key={index} onClick={ this.handleClick.bind(this) }/>
})
}
</div>
)
}
The repositories is changing the way I want, but for some reason the its not get re-rendered. I passing the repositiores property from the parent.
The first time I render it (click to the search button, get a response from the server, and set the repo array), its working fine. But at the 2nd search, when there is something in the array, its not working properly, and not re-render.
UPDATE:
The parent's render / onClick
render() {
const {repositories} = this.state
return (
<div className='w-third navpanel br b--black-20'>
<SearchBar onClick={this.onClick} onChange={this.onChange}/>
<RepoList repositories={repositories}/>
</div>
//<NewNote />
//<Tags />
//<NoteList />
);
}
onClick = (event) => {
const {searchTerm} = this.state
let endpoint = 'https://api.github.com/search/repositories?sort=stars&order=desc&q=' + searchTerm;
fetch(endpoint)
.then(blob => blob.json())
.then(response => {
if(response.items)
this.setState({ repositories: response.items });
})
}
UP-UPDATE:
Search Comp:
constructor({onClick, onChange}) {
super()
this.onClick = onClick
this.onChange = onChange
this.state = {
imageHover: false
}}
render() {
return (
<div className='flex items-center justify-between bb b--black-20'>
<div className='ma2 inputContainer w-100'>
<input className='pa1 pl4 boardingbox w-100 input-reset ba b--black-20 br4 black-50 f6' placeholder='repos' type="text" onChange={this.onChange}/>
</div>
<div className='mr2'>
<div className='boardingbox pointer contain grow'>
<img src={(this.state.imageHover) ? NoteImageOnHover : NoteImage} alt=''
onMouseOver={()=>this.setState({imageHover: true})}
onMouseOut={()=>this.setState({imageHover: false})}
onClick={this.onClick}/>
</div>
</div>
</div>
)}
first responde
second responde
and I am really ashamed that I could screw up like this.
So basicly the problem was:
return <Note name={repo.name} desc={repo.name} key={index} onClick={ this.handleClick.bind(this) }/>
So I was as stupid to use INDEX as a KEY so I could not add again the same key to the array.
Thanks anyway guys! :)
The root cause most probably is due to error in function binding.
In your SearchComponent you are using the "props" to create function bindings in the contructor. This can cause your SearchComponent to refer to wrong instance of the functions for onClick and onChange. Would suggest referring to the official documentation for more details.
you do not need to rebind the functions in your SearchComponent, you can just use the functions received in props.
<input className='pa1 pl4 boardingbox w-100 input-reset ba b--black-20 br4 black-50 f6' placeholder='repos' type="text" onChange={this.props.onChange}/>
<!-- snipped other irrelevant code -->
<img src={(this.state.imageHover) ? NoteImageOnHover : NoteImage} alt=''
onMouseOver={()=>this.setState({imageHover: true})}
onMouseOut={()=>this.setState({imageHover: false})}
onClick={this.props.onClick}/>
Why could be happening to cause this behavior
Remember, constructor is only called once the component instance is being constructed, once it has been created and remains alive, React lifecycles take over.
So, when you first render your screen, the component is created and since there is only 1 of everything, it kind of works.
When you run your first search: onChange/onClick callbacks modify the state of the parent component. Which then calls render on the parent component.
At this point, it is possible that your SearchComponent maybe holding on to the wrong instance of the call back methods, which would thus not set state on the parent and thus not force re-render.
Additional Notes on your constructor
Normally you shouldn't refer to props in your constructor, but if you need to, then you need to have it in the format below. Here are the relevant docs:
constructor(props) {
super(props);
// other logic
}

Categories

Resources