Pass data between siblings without Redux - reactjs

Consider the following component tree
<Header />
<SearchBar />
<ProductList />
<Product />
In the SearchBar component I want to catch the value of an input and send that value to ProductList to dynamically render Product components based on the value received.
Is there a way to communicate between SearchBar and ProductList without the need of a component that wraps both of them or without Redux ?

According to React's page "React is all about one-way data flow down the component hierarchy" which means that data passing horizontally should be avoided.
In your case you could have a parent component that render <SearchBar/> and <ProductList/>based on state. For example, whenever the user enter with a value in <SearchBar> it changes the state on the parent component, and consequently <ProductList> will be rendered again.

As Osman said, you could pass the value of the SearchBar into a state and then from the state into the Product using a function.
handleChange(event) {
this.setState({
newProduct: event.currentTarget.value
)}
}
render() {
return(
<SearchBar onChange={this.handleChange} />
<Product content={this.state.newProduct} />
);
}
Make sure you don't forget to bind the function in the constructor.

Related

How does the 'key' prop work in a non-dynamic child component and why is it essential for rendering updates?

I understand that the special 'key' prop when used with child components that are created dynamically from arrays helps React to uniquely identify components and render updates efficiently. But I would like to know when and why would it be necessary to use the key prop for a 'non-dynamic' component.
My application uses a Reducer and useContext hook to manage state for a Functional Component A. The state object has a maximum 3 levels of nesting. Component A updates state and passes part of the state object as props to two instances of a child component B. B uses these props to render a switch component and 2 input components. Here's the simplified code for this hierarchy.
Component A:
const A: FC = () => {
// ....
// graphql queries to get data and update state using reducer
// ...
return (
<B
enabled={data.a.b.enabled}
value1={data.a.b.value1}
value2={data.a.b.value2}
/>
<B
enabled={data.a.b.enabled}
value1={data.a.b.value1}
value2={data.a.b.value2}
/>
);
};
Component B:
const B: FC = props => {
const { value1, value2, enabled} = props; // there are other props as well
return (
<>
<div className={someClassLogic}>
<Switch
onChange={onValueChange}
isChecked={enabled}
disabled={disabled}
/>
</div>
<div className={someClassLogic} >
<Input
input={value1}
disabled={disabled}
/>
</div>
<div className={someClassLogic}>
<Input
input={value2}
disabled={disabled}
/>
</div>
</>
);
};
A tablerow click event is used to update the state and the component B displays the 'settings' of this selected item which the user can mutate using the component B.
Here's the problem I'm facing- when the state is updated by a user action (selecting a row from a table, not shown in the snippet), I can see that both A and B receive the new data in the react developer tools and by printing to the console. But, a render does not take place to show the new data. I would like to understand why this is the case.
After looking up this issue, I figured I need a key prop while instantiating component B (the answers don't clearly explain why). With the following addition, the values did render correctly. Why is a key necessary here and why does it only work when the key contains all props that can change values? If I only use the uniqueId as the key, the value1 and value2 do not render correctly again. If I have many changing-props, do I have to them to add the key as well? Isn't there a less clumsy approach?
Updated Component A:
const A: FC = () => {
return (
<B
key={`${data.a.uniqueId}-
${data.a.b.value1}-
${data.a.b.value2}
enabled={data.a.b.enabled}
value1={data.a.b.value1}
value2={data.a.b.value2}
/>
<B
key={`${data.a.uniqueId}-
${data.a.b.value1}-
${data.a.b.value2}
enabled={data.a.b.enabled}
value1={data.a.b.value1}
value2={data.a.b.value2}
/>
);
};
Also, I noticed that although clicking on a table row rendered the correct value in component B now, but clicking on a row that is not changed by the user so far would cause the previously rendered values to remain on the Input1 and Input2 components (instead of blank). So I had to add keys to the Inputs as well with the enabled state attached to it which fixed this issue.
Updated Component B:
const B: FC = props => {
const { value1, value2, enabled} = props; // there are other props as well
return (
<>
<div className={someClassLogic}>
<Switch
onChange={onValueChange}
isChecked={enabled}
disabled={disabled}
/>
</div>
<div className={someClassLogic} >
<Input
key={`value1-${enabled}`}
input={value1}
disabled={disabled}
/>
</div>
<div className={someClassLogic}>
<Input
key={`value2-${enabled}`}
input={value2}
disabled={disabled}
/>
</div>
</>
);
};
Here again, why is a key needed? Doesn't react figure out that the props have changed and automatically render again?

How to pass this.props from parent to child component while using map() function without getting undefined?

I have two components: <Content /> as the parent, and <Curriculum /> as the child.
For the <Content /> component, after getting state from Redux store, I mapped from state to props and passed these props into the map() function.
// import...
class Content extends Component {
render() {
return (
<div>
{this.props.englishEntrances.map((english, i) => {
//Work
console.log(`CONTENT ${i}`);
console.log(this.props.curriculums);
console.log(this.props.curriculums[i]);
console.log(english);
return (
<Curriculum
key={i}
level={i + 1}
titleColor={colorLevels[i].title}
contentColor={colorLevels[i].content}
//Bug here
curriculum={this.props.curriculums[i]}
englishEntrance={english}
/>
);
})}
</div>
);
}
}
//const mapStateToProps...
export default connect(mapStateToProps, null)(Content);
Inside the map() function, there are two array of object: curriculums and englishEntrances.
I tried to print this.props.curriculums[i] and english, the console will appear these values normally.
The problem is: When i tried passing both values in return statement, in the <Curriculum /> component, only the english has the value, but this.props.curriculum is undefined.
Anyone know how to pass this.props value without getting undefined?
I forgot that the render() function in the life cycle of a React component can render right after initializing the constructor. In <Curriculum /> component, the value of this.props.curriculum will be undefined at this stage,
Therefore I have to handle event in that compoent before and after getting state from the store.

How to get react grand-child node to render when child is out of my control

I have a react component which uses a 3rd party library Component as a Child node. The Grand children (or the children of the 3rd party libary) are under my control. When my component receives new props it re-renders, however the 3rd party component seems to stop my components grand-children from re-rendering also, even though the props my component received, are passed to the non-re-rendering components directly
If I remove the 3rd party component then my component re-renders as do the grand-children.
render() {
<div>
<ThirdPartyComponent props={blah}>
{this.props.products.map(prod => <MyGrandChildrenComponents product={prod} />
</ThirdPartyComponent>
</div>
}
A concrete example can be found on this code sandbox: codesandbox.io/s/silly-grass-7frlx
I'd expect my MyGrandChildrenComponents component to get updated when this.props.products changes... Any hints?
This can happen if ThirdPartyComponent is a stateful component and is not handling its prop updates correctly. One way to force a re-render is to add a key prop to your ThirdPartyComponent and update its value when a re-render is needed.
render() {
<div>
<ThirdPartyComponent key={something-that-changes-when-rerender-needed} props={blah}>
{this.props.products.map(prod => <MyGrandChildrenComponents product={prod} />
</ThirdPartyComponent>
</div>
}
If ThirdPartyComponent is a PureComponent it should re render (and so re render its children) when one of its props changes so you can try:
render() {
<div>
<ThirdPartyComponent props={blah} products={this.props.products}>
{this.props.products.map(prod => <MyGrandChildrenComponents product={prod} />
</ThirdPartyComponent>
</div>
}
To trigger re renders when this.props.products changes.
But any prop should do.
If ThirdPartyComponent has a custom implementation of shouldComponentUpdate, then you will have to find the specific prop which triggers update if it exists.

How to set props dynamically in input field

I want to create a dynamic input component using props
This is App.js File
<TextBox type="text" style="singIn" placeholder="hi there" />
<TextBox type="password" style="singUp" />
This is TextBox.js File
import React, { Component } from 'react'
class TextBox extends Component {
constructor(props) {
super(props)
}
render() {
const {type, value, ...other} = this.props
return (
<div>
<input type={type} value={value} {...other} />
</div>
)
}
}
export default TextBox
You can use state to set value dynamically, Instead of props.
Or
You can use state to set the value to props. So that if you change the state value automatically props value also will change.
You're on the right track!
In TextBox, what you can do in your render() method is to pass on all of the props to <input> using the Object spread operator. This passes on all of the props as key value pairs. So, you can do:
<input {...this.props} />
That essentially does:
<input type={this props.type}
value={this.props.value}
placeholder={this props.placeholder}
// and so on for every single key-value pair you have in props
/>
But, there is a slight problem. If you pass style as a prop, it must be "a JavaScript object with camelCased properties". That is, you can't pass "signIn" or "signUp" (which are just strings) as a style. Perhaps what you meant to do is pass them as a class prop (meaning, set the CSS class of the input to "signIn" or "signUp").
Here is a code sandbox that demonstrates using the Object spread operator to pass all of the props on to the child input.

Component class not rendering

I'm having a weird problem with a self-made ReactJS component and I must be doing something fundamentally wrong.
I have a parent component, which periodically receives an array of objects by means of a REST service. The received elements are enumerated and put into a react-bootstrap ListGroup element:
<Row>
<Col sm={2}>
<ListGroup>
{this.state.maps.map((object, index) => {
return <ListGroupItem key={index}
onClick={() => this.setState({ currentMapSelection: object })}>
{object.map_name}
</ListGroupItem>
})}
</ListGroup>
</Col>
<Col sm={10}>
{this.renderMapForm()}
</Col>
</Row>
The display of the ListGroupItems is working fine.
Now I wanted to edit an List element by clicking on it. For code separation I wanted to put the entire edit logic into a separate component (MapLoadForm), which renders some Form elements and means to update and delete.
I put the rendering into a separate function, which conditionally renders the MapLoadForm:
renderMapForm() {
const mapForm = this.state.currentMapSelection;
if (mapForm != undefined) {
return <MapLoadForm map={mapForm}></MapLoadForm>
}
return <div></div>
}
The problem now is, that the MapLoadForm just renders correctly for the very first click. Subsequent clicks to other list items don't change it anymore. I don't see the call the the MapLoadForm constructor anymore.
If I replace the return <MapLoadForm map={mapForm}></MapLoadForm> by return <Button>{mapForm.map_name}</Button>, I see a new button rendered with every new click. The same happens, if I put the entire Form rendering logic into the return statement of the renderMapForm() function. Just the "outsourcing" into a new component doesn't work.
import React from 'react';
...
export class MapLoadForm extends React.Component {
constructor(props) {
super(props);
}
...
render() {
return (
<Form style={{ marginTop: "20px" }} horizontal>
...
</Form>
)
}
}
export default MapLoadForm;
Question now: Should that work or am I on the completely wrong track?
TIA
You are missing one part: Whenever props of any component changes, react re-render that component but re-rendering doesn't mean unmounting and mounting the component. It means same component will be rendered with new props, constructor will not get called again.
Put this console inside render method of MapLoadForm component, you will see new value each time component re-render:
console.log('props', this.props.map)
Solution:
What you are looking for is, componentWillReceiveProps lifecycle method, whenever component received new props, this lifecycle method will get called, do the calculation again with new props values, like this:
componentWillReceiveProps(nextProps){
console.log('new props', nextProps);
}
componentWillReceiveProps
componentWillReceiveProps() is invoked before a mounted component
receives new props. If you need to update the state in response to
prop changes (for example, to reset it), you may compare this.props
and nextProps and perform state transitions using this.setState() in
this method.

Resources