Share data between React components with no relation? - javascript

I'm working on a React component library that allows for client-side data filtering by passing an array of objects and an <input/> as props to a <SearchFilter/> component. I want to return the filtered results to a separate <SearchResults/> component that can be rendered elsewhere in the tree (i.e. the results component doesn't have to be a child of the input component).
I've got the filtering figured out, but I'm not sure the best route to take in React on getting the filtered data to the <SearchResults/> component.
This is what I'd like to end up with...
<SearchFilter
data={data}
input={<input type="text" value={value} onChange={this.handleChange}/>}
/>
Then, using Render Props to return the data and map over that to return JSX, there would be the results component. Something like this...
<SearchResults
render={data => (
data.map(el => (
<div>
<span>{data.someProperty}</span>
</div>
)
)}
/>
This is what I'd like to achieve because I want to allow for rendering the <SearchFilter/> component at one place in the tree, and allow the <SearchResults/> component to be rendered elsewhere, so that there's maximum flexibility in how the tree is composed and, therefore, how the view is rendered.
I've looked into the Context API, but it seems like that would require a handful more components to be a part of my library, which further complicates what I'm trying to achieve. If that's the only way around it, then that's fine, but I wanted to ask and see if anyone can think of another solution.
Thanks!

The bigger issue is that you will need to manage a state that is shared between components on a higher level, i.e., any component that will wrap these other two components, ultimately. With plain React, this state would be managed by the parent (or ancestor) component, passing down the relevant values as props. This opposed to the, usually bad, idea to have sibling components influence each other's state, since you well get into the "who's boss here"-problem.
The thing the Context API handles is not having to pass down props for things that typically don't change (or: typically shouldn't cause renders to trigger often).
A global state store, such as Redux, can help you modelling this, but in essence it's not much more than 'a' component managing state, and other components rendering according to that state. Events within the lower components trigger changes in the data, which will cause the state to change, which will cause the props of the children to change, which then will cause re-renders.
I'd advise you to try using this simple pattern:
class Search ... {
state = {data: [], text: ""}
render() {
return (
<div>
<SearchFilter
data={this.state.data}
onSearch={() => this.fetchNewData()}
onChange={(e) => this.setState({text: e.targetElement.value})}
text={this.state.text}
/>
<SearchResults data={this.state.data} />
</div>
);
}
fetchNewData() {
fetch('/url?text=' + this.state.text)
.then((newData) => { this.setState({data: newData}); })
}
}
Something along these lines. If you have trouble modelling stuff like this, you can use Redux to force you to do it in a similar way, and avoid managing local state intermixing with global state (which is typically something that is hard to manage).
If you do this right, components that have no state (i.e., aren't responsible for managing state and thus have no event handlers) can all become pure components, i.e. stateless components, i.e. functions that return JSX based on props:
const SearchResults = ({data}) => <div>{data.map( () => <etc /> )}</div>

You could create a data store class that holds your filter, pass it in as a property to both components, and have your SearchFilter component change a value in that.

Related

How a component pass a prop to another component?

Newbie here, I am studying the documentation of react and in React Context API, I couldn't understand something, I won't understand the rest of the subject if I don't understand it. Can anyone help me what does it mean through using an example?
The Toolbar component must take an extra "theme" prop
and pass it to the ThemedButton. This can become painful
if every single button in the app needs to know the theme
because it would have to be passed through all components.
class App extends React.Component {
render() {
return <Toolbar theme="dark" />;
}
}
function Toolbar(props) {
// The Toolbar component must take an extra "theme" prop
// and pass it to the ThemedButton. This can become painful
// if every single button in the app needs to know the theme
// because it would have to be passed through all components.
return (
<div>
<ThemedButton theme={props.theme} />
</div>
);
}
class ThemedButton extends React.Component {
render() {
return <Button theme={this.props.theme} />;
}
}
The Toolbar component must take an extra "theme" prop
this can be like <Toolbar theme="dark">
and pass it to the ThemedButton
how Toolbar component pass this prop to ThemedButton? and kindly clarify the rest of the comment as well.
Thank you for any help? You are kind
In your Toolbar component, it takes a parameter props, props is whatever properties have been passed to it when calling it, as in <Toolbar param1="someString" param2={someVariable}>, in this case the props value in Toolbar will be an object with the data you passed as key=value like for example: {param1: "someString", param2: content_of_someVariable}
And if you don't actually use those props (properties)/parameters in Toolbar, but rather in a subcomponent, then you have to pass them again to another level, like in <ThemedButton theme={props.theme} />, then ThemedButton itself finally passes the value to the component that actually makes use of, which is in your case: <Button theme={this.props.theme} />;.
So you had to pass the theme across multiple components, which don't use it or care at all about it, just to get it through to the final Button component.
(answer ends here, below is my effort to explain context API in an easy way)
To avoid that annoying level to level to another..., you can use the context API. Because it is really incontinent to pass a value across 3-4+ levels/components every time you want to use it in the last one in the chain.
Think about the context like a variable defined and exported on a root level and holds some data (like the user login status for example, or the theme infomation), and whenever you require that data, you import it and use it directly. You use the Provider property of the context you define (MyContext.Provider) to assign the data to it, and you use the Consumer property (MyContext.Consumer) to consume/access that data you assigned in the provider.
The beauty of the context consumer, is that whenever the data is updated in the provider, the consumer immediately gets the new data and triggers a re-render with the new data.
I hope I explained it in a simple and clear way. Write a comment with any questions or unclear parts and I can try my best to improve the answer.
Best of luck!
Props are properties that help define the way your JSX appears on the page.
When you use a component that you have created, you can pass it props like below:
<MyComponent myProp={myPropValue} />
You can continue to pass props down through the component hierarchy as well. So say you have a component tree like below:
MyComponent
--MySubComponent
----MySubSubComponent
You can pass props from MyComponent to MySubSubComponent like so:
<MyComponent myProps={MyProps} />
<MySubComponent mySubProps={props.myProps} /> //Props are the value you gave in the parent component
<MySubSubComponent mySubSubProps={props.mySubProps} />
Whatever title you give the props when declaring the component in JSX is the title you will call to get the value of the prop like props.myProps

React changing state in Parent , does render calls for all its children and sub-children as iteration?

i am setting the language name in my local storage , when it changes from a dropdown in topbar , i want the whole current view to be re-rendered and words translated to the selected language. my layout is like this
render(){
return (
<MainContainer>
<TopBar/>
<SideBar/>
<RouteInsideSwitch/>
</MainContainer>
)
}
in render of components ,the words to be translated basically calls a function that returns the correct word based on the local storage language name.
i change the language and i set the state in maincontainer for selected langauge and set it in local storage. however i dont want to move that state from Maincontainer to all my components. also dont want to store it in redux because then all the possible containers have to listen to it and then pass it to their children as props.
what currently happens is that saving state in mainContainer without passing it to any children , the children does re-render but only the immediate ones , if there are more children in those children and so on , it does not re-render because i m not passing the state throughout the chain.
open to any suggestion based on different pattern for language changing. but my question is that is there any way to re-render the current open view (all components in dom).
If your concern is that you have a number of "possible containers" which all need to handle the state change, perhaps consider creating a higher order component that includes the common language rendering logic (your RouteInsideSwitch leads me to believe this may the issue). In that way, you can avoid duplicating that logic across a ton of "possible" components that all require the functionality of dynamic language rendering and will avoid the need to dial a bunch of components into a redux store, assuming they are in the same hierarchy.
const DynamicLanguageComp = RenderComponent => {
return class extends Component {
constructor(props) {
super(props)
//additional state setup if needed
}
changeLangFunc = () => { /* handle change */ }
render() {
return <RenderComponent handleLanguageChange={this.changeLangFunc} {...this.props} {...this.state} />
}
}
}
If you would like to avoid a re-render on certain intermediate components that may be receiving props by way of state change you can implement the lifecycle method shouldComponentUpdate(), which by default returns true. You can make a comparison of nextProps to your current props, and return false if a re-render is undesired despite new props.

Can I share a component instance across multiple trees in React?

I've been creating an API to help manage state machines in React.
It consists of three components:
<StateMachine>: Receives an xstate machine as a prop, and sets up a context for deeper components to use.
<StateView>: Receives two props: state & children, and renders its children only if that state is currently active.
<StateControl>: Receives some arbitrary props - each being an event used to transition the machine - and converts them to transition callbacks to be passed down to its children (which is NOT an element, but an elementType as determined by PropTypes).
Here is a visual representation of what is at play:
Using React's context API, I can flexibly switch on/off nodes in the React tree based on the state of the machine. Here's a sample code snippet demonstrating this:
const MyMachine = () =>
{
return (
<StateMachine machine={sampleMachine}>
<StateView state="initializing">
<StateControl onSuccess="success">
{MySampleInitializer}
</StateControl>
</StateView>
<StateView state="initialized">
<p>{"App initialized"}</p>
</StateView>
</StateMachine>
);
This works great! When the machine is in the "initializing" state, MySampleInitializer gets rendered. When initialization is complete, onSuccess is called which transitions the machine to "initialized". At this point, the <p> gets rendered.
Now the problem:
In most situations, each "state view" would render a different component (which gets created & mounted when the appropriate state becomes active).
However what if we wanted to apply the machine to a single component only? For example, I have a <Form> component which handles the rendering of some form elements, and should receive different props depending on the state the form is currently in.
const MyFormMachine = () =>
{
return (
<StateMachine machine={formMachine}>
<StateView state="unfilled">
<StateControl onFill="filled">
{(props) => <MyForm {...props} disableSubmit/>}
</StateControl>
</StateView>
<StateView state="filled">
<StateControl onClear="unfilled" onSubmit="submit">
{(props) => <MyForm {...props}/>}
</StateControl>
</StateView>
<StateView state="submitting">
<MyForm disableInput disableSubmit showSpinner/>
</StateView>
</StateMachine>
);
Using my current API, rendering a <MyForm> within each <StateView> will cause <MyForm> to be re-mounted anytime a state change happens (thereby destroying any internal state associated with it). The DOM nodes themselves will also be re-mounted, which may re-trigger things like autofocus (for instance).
I was hoping there may be a way to share the same <MyForm> instance across the various "views" such that this re-mounting does not occur. Is this possible? If not, is there an alternative solution which would fit with this API?
Any help greatly appreciated.
PS: If the question title is unsuitable, please suggest a change so that this question may more accessible. Thanks
The problem is that the components inside your StateView instances are always constructed regardless of whether you are in the correct state or not.
So in your form example, you always have 3 Form instances, but only 1 rendered at a time. As you've stated, you can only have 1 Form instance in order to maintain state and prevent re-mounting.
When you are passing a conditional component (MyForm) into another component (StateView), you should always wrap it inside a function.
Your StateView class can then only create the MyForm instance if you're in the correct state.
You now have only 1 instance at a time (assuming only 1 state is matched at a time), but each StateView still has its own instance which isn't shared.
From what I know, you cannot avoid separate instances unless inside the same parent component.
I would change your StateView component to handle multiple state checks instead of one. This way, your instances will be reused when the state changes (again, assuming only 1 state is matched at a time).
Your StateView construction could look something like this:
<StateMachine machine={formMachine}>
<StateView>
{{
"unfilled": () => (
<StateControl onFill="filled">
{(props) => <MyForm {...props} disableSubmit/>}
</StateControl>
),
"filled": () => (
<StateControl onClear="unfilled" onSubmit="submit">
{(props) => <MyForm {...props}/>}
</StateControl>
),
"submitting": () => (
<StateControl>
{(props) => <MyForm disableInput disableSubmit showSpinner/>}
</StateControl>
)
}}
</StateView>
</StateMachine>
Note that in order to reuse a component, the component has to be of the same type. I wrapped the 3rd MyForm in an empty StateControl so that each state constructs a StateControl, and therefore the components can be reused.
Also note that if you do have multiple states matched at a time inside a single StateView, you can give each StateControl a key property. The same key property value should not be used on two components that may be instantiated at the same time. An easier solution would be to just have separate StateView instances, where each one will only match a single state at a time.

React: Bubbling up click events on nested components

I'm creating a react file tree, and I have the tree setup as a React component. The tree can take a contents prop that is an array of either strings, or other <Tree /> components (this enables the nested file structure UI). These tree components can be nested indefinitely.
I need to register a click event on the children of the nested tree components, but I'm having trouble getting it to work beyond the first level of nesting. A simplified example of what I'm dealing with:
//In App - the top level component
const App = React.createClass({
_handleChildClick () {
console.log("this is where all child clicks should be handled");
},
render () {
return (
<Tree
handleChildClick={this._handleChildClick}
contents={[
<Tree />
]}
/>
);
}
});
//And in the tree component
<div onClick={this.props.handleChildClick}></div>
If you want to see more detail - here's the github repo.
I tried researching this question and saw people using {...this.props} but I'm not sure if that applies to my scenario - if it does, I couldn't get it to work.
Thanks for any help on this.
The reason why the click handling does not work beyond the first level is because your second level Tree component (the one inside the contents array) does not get the appropriate prop handleChildClick passed in. (BTW I think the convention is to call the prop onChildClick while the handler function is called handleChildClick - but I digress.)
Do I understand correctly that you actually want to inform each layer from the clicked component up to the top? For this to happen, you need to extend the props of the tree component that is inside the contents array - it needs to receive the click handler of its parent component. Of course, you cannot write this down statically, so it needs to be done dynamically:
Your Tree component, before actually rendering its children, should extend each of them with the component's click handler, which can be done using the function React.cloneElement (see API documentation and a more detailed discussion). Directly applying this to your component makes things a bit messy, because you are passing the component's children in a prop, so you need to figure out which prop to modify. A bit of a different layout would help you quite a lot here:
<Tree handleChildClick={this._handleChildClick}>
<Tree />
</Tree>
looks nicer IMHO and makes the structure much clearer. You can access the inner components via this.props.children, and cloneElement will be much simpler to use.
So, in your Tree component, you could have a render method like this:
render () {
const newChildren = this.props.children.map(child =>
React.cloneElement(child, {onChildClick: this._handleChildClick}));
return (
<div>{newChildren}</div>
);
}
Please note that this code will not work if you have a mixture of strings and Tree components, therefore my third and last suggestion would be to wrap those strings into a very thin component to allow for easier handling. Alternatively, you can of course do a type comparison inside the map.

Why can't I update props in react.js?

Why do we have both state and props? Why don't we just have one source of data? I'd like to update a component's props and have it re-render itself and all of its children. Seems simple but I can't figure out how to let a component update its own or its parent's props.
Thanks for any help.
The React philosophy is that props should be immutable and top-down. This means that a parent can send whatever prop values it likes to a child, but the child cannot modify its own props. What you do is react to the incoming props and then, if you want to, modify your child's state based on incoming props.
So you don't ever update your own props, or a parent's props. Ever. You only ever update your own state, and react to prop values you are given by parent.
If you want to have an action occur on a child which modifies something on the state, then what you do is pass a callback to the child which it can execute upon the given action. This callback can then modify the parent's state, which in turns can then send different props to the child on re-render.
To answer the question of why
In React, props flow downward, from parent to child.
This means that when we call ReactDOM.render, React can render the root node, pass down any props, and then forget about that node. It's done with. It's already rendered.
This happens at each component, we render it, then move on down the tree, depth-first.
If a component could mutate its props, we would be changing an object that is accessible to the parent node, even after the parent node had already rendered. This could cause all sorts of strange behaviour, for example, a user.name might have one value in one part of the app, and a different value in a different part, and it might update itself the next time a render is triggered.
To give a fictional example:
// App renders a user.name and a profile
const App = (props) =>
React.createElement('div', null, [
props.user.name,
React.createElement(Profile, props)
])
// Profile changes the user.name and renders it
// Now App has the wrong DOM.
const Profile = ({user}) => {
user.name = "Voldemort" // Uh oh!
return React.createElement('div', null, user.name);
}
// Render the App and give it props
ReactDOM.render(
React.createElement(App, {user: {name: "Hermione"}}),
document.getElementById('app'))
);
We render app. It outputs "Hermione" to the Shadow DOM. We render the Profile, it outputs "Voldemort". The App is now wrong. It should say "Voldemort" because user.name is "Voldemort", but we already output "Hermione", and it's too late to change it.
The value will be different in different parts of the app.
Modifying Props would be two-way-binding
Mutating props would be a form of two-way binding. We would be modifying values that might be relied on by another component higher up the tree.
Angular 1 had this, you could change any data anytime from wherever you were. In order to work, it needed a cyclical $digest. Basically, it would loop around and around, re-rendering the DOM, until all the data had finished propagating. This was part of the reason why Angular 1 was so slow.
In React, state and props serve different goals: state allows a component to maintain some changing values, while props are the mecanism to propagate those values to children.
Children are not allowed to alter by themselves the values they get via props just because React designers find it easier to maintain an application built this way. Their point is that when only one component is allowed to update some piece of state, it is easier to discover who altered it, and find the root of bugs.
the Component itself changes its state, and changes not its own, but the children's props.
<Parent>
<Child name={ this.state.childName } />
</Parent>
Parent can change its own state and change the child name, but it will change the props for his children.
edit1:
for calling events from the child to its parent, you should pass in the child an event handler like so:
var Child = React.createClass({
render: function() {
return (<button onClick={ this.props.onClick }>Hey</button>);
}
});
var Parent = React.createClass({
onChildClick: console.log.bind(console), // will print the event..
render: function() {
return (<Child onClick={ this.onChildClick } />);
}
});
React.renderComponent(<Parent />, document.body);
in this code, when you'll click on the Child's button, it will pass the event to its parent.
the purpose of passing the events is decoupling the components. maybe in your app you need this specific action, but in another app you'll have, you'll use it differently.
My solution was fairly different but some people might run into it. On the Chrome Dev tools, it kept saying that my props were read-only and when I tried passing them down even further, I would get an error. Now, the reason why is because I wasn't invoking a render() method. I was instead calling my component like this:
const Navigation = () =>{
return (
<div className="left-navigation">
<ul>
<Link to='/dashboard'><li>Home</li></Link>
<Link to='/create-seedz'><li>Create Seedz</li></Link>
<Link to='/create-promotion'><li>Create Promotion</li></Link>
<Link to='/setting'><li>Setting</li></Link>
<SignOutButton />
</ul>
</div>
);
}
I added a render method and it solved my issue of being able to pass props down:
class Navigation extends Component{
render(){
return (
<div className="left-navigation">
<ul>
<Link to='/dashboard'><li>Home</li></Link>
<Link to='/create-seedz'><li>Create Seedz</li></Link>
<Link to='/create-promotion'><li>Create Promotion</li></Link>
<Link to='/setting'><li>Setting</li></Link>
<SignOutButton user={this.props.user} signedOut={this.props.signedOut} authed={this.props.authed}/>
</ul>
</div>
);
}
}
Hopefully this helps someone.
Contrary to the answers provided here, you actually can update props directly, if you don't mind defying the pedantic circlejerk about "the React way." In React.js, find the following lines of code:
Object.freeze(element.props);
Object.freeze(element);
and comment them out. Voila, mutable props!

Categories