Load component but without part of its structure - reactjs

I'm trying to load in a view a component which is: <Loader/>
The thing is that the component is structured like this:
<Loader.BoxTitleContainer>
<Loader.MainTitle>
<Loader.Title>{`Loading...`}</Loader.Title>
</Loader.MainTitle>
</Loader.BoxTitleContainer>
<Loader.Container className={'loader'}>
<Loader textDisabled={true} />
</Loader.Container>
What I actually want to do is to load <Loader/> the way it is but preventing it to load <Loader.BoxTitleContainer> without altering the structure (which is on top of this paragraph). How can I do it? Any solid example?
Thanks!

You would need to add a boolean prop to your Loader component which would render BoxTitleContainer if set to true, and vice versa, like this:
const { showBoxTitleContainer } = this.props;
return (
{showBoxTitleContainer && <Loader.BoxTitleContainer>
<Loader.MainTitle>
<Loader.Title>{`Loading...`}</Loader.Title>
</Loader.MainTitle>
</Loader.BoxTitleContainer>}
<Loader.Container className={'loader'}>
<Loader textDisabled={true} />
</Loader.Container>
);
The alternative would be to divide the structure in two React components:
// BoxTitleContainer
<Loader.BoxTitleContainer>
<Loader.MainTitle>
<Loader.Title>{`Loading...`}</Loader.Title>
</Loader.MainTitle>
</Loader.BoxTitleContainer>
// LoaderContainer
<Loader.Container className={'loader'}>
<Loader textDisabled={true} />
</Loader.Container>
You can't accomplish this without altering the structure.

if Loader.BoxTitleContainer is your's component, you can introduce a property, let say, hide, or if that is 3dparty component see it's possible properties (sometimes it is better just to see the source code, as docs sometims missing some details). Inside the BoxTitleContainer there should be a check for value of hide for the decision on either render the component or not.
In this approach you can easily control your title like (see hide={this.props.hideTitle || false} as a property for BoxTitle):
<Loader.BoxTitleContainer hide={this.props.hideTitle || false}>
<Loader.MainTitle>
<Loader.Title>{`Loading...`}</Loader.Title>
</Loader.MainTitle>
</Loader.BoxTitleContainer>
<Loader.Container className={'loader'}>
<Loader textDisabled={true} />
</Loader.Container>
and then in your <Loader /> you can do <Loader hideTitle={true}/>
If the component is not yours and it doesn't have any similar to hide property, then you can do HOC component to wrap this BoxTitle one and then introduce the hide propery in it.

Related

React: best practice for state control

Lately I've noticed that I'm writing a lot of React code were I have 2 components:
Data filter
Data display
In the first component I might have N possible filters to apply to the data. These filters often complex components on their own. When the user defines the filters I need to pass it from 1st component to the 2nd for display.
Example: https://codesandbox.io/s/wonderful-sutherland-16zhp8?file=/src/App.js
What I see in many cases to happen is that I manage the state of the filter in multiple places.
In the example above, I have it in the Toolbar component and in the parent one.
What is the best way of managing states in such case?
You should definitely never duplicate the state in multiple places as this can create bugs where they get out of sync. The solution is to hoist state and then pass it down via props, see https://beta.reactjs.org/learn/sharing-state-between-components.
Here is your codesandbox modified as an example: https://codesandbox.io/s/keen-wilbur-0xyi8f
You are duplicating the sate multiple times, plus there are some unnecessary helper functions along the way.
The state can be held in one place, with its setter function passed to the toolbar component, like so:
export default function App() {
const [searchStr, setSearchStr] = useState("");
return (
<div className="App">
<h1>Tony Starks Nicknames</h1>
<Toolbar searchStr={searchStr} onSearchStrChange={setSearchStr} />
<List searchStr={searchStr} />
</div>
);
}
Then, the toolbar can look like this:
function Toolbar({ searchStr, onSearchStrChange }) {
const handleChange = (e) => {
onSearchStrChange(e.target.value);
};
return (
<div>
<input
type="text"
onChange={handleChange}
value={searchStr}
placeholder="search..."
/>
</div>
);
}
This way, there's only one place where the state is stored
If your hierarchy's depth is or might become greater than 2, I would suggest the Context API.
https://reactjs.org/docs/context.html

React Context always returns EMPTY

I have a Search parent component and a SideBar child component, I am trying to get context in SideBar, but everytime it returns empty.
I followed the tutorial exactly like: https://itnext.io/manage-react-state-without-redux-a1d03403d360
but it never worked, anyone know what I did wrong?
Here is the codesandbox link to the project: https://codesandbox.io/s/vigilant-elion-3li7v
I wrote that article.
To solve your specific problem:
When using the HOC withStore you're injecting the prop store into the wrapped component: <WrappedComponent store={context}.
The value of the prop store is an object that contains 3 functions: get, set, and remove.
So, instead of printing it, you should use it. For example this.props.store.get("currentAlbums") or this.props.store.set("currentAlbums", [album1, album2]).
This example is forked by your code: https://codesandbox.io/s/nameless-wood-ycps6
However
Don't rewrite the article code, but use the library: https://www.npmjs.com/package/#spyna/react-store which is already packed, tested, and has more features.
An event better solution is to use this library: https://www.npmjs.com/package/react-context-hook. That is the new version of the one in that article.
This is an example of a sidebar that updates another component content: https://codesandbox.io/s/react-context-hook-sidebar-xxwkm
Be careful when using react context API
Using the React Context API to manage the global state of an application has some performance issues, because each time the context changes, every child component is updated.
So, I don't recommend using it for large projects.
The library https://www.npmjs.com/package/#spyna/react-store has this issue.
The library https://www.npmjs.com/package/react-context-hook does not.
You pass the store as a prop, so to access it, you need this.props.store in your SideBar.
Not this.state.store
Create a wrapping App component around Search and Sidebar:
const App = props => (
<div>
<Search />
<SideBar />
</div>
);
export default createStore(App);
Now you can manipulate state with set and get that you have available in child components Search and Sidebar.
In Search component you can have something like:
componentDidMount() {
this.props.store.set("showModal", this.state.showModal);
}
also wrapped with withStore(Search) ofc.
and in SideBar you can now call:
render() {
return (
<div>
{"Sidebar: this.state.store: ---> " +
JSON.stringify(this.props.store.get("showModal"))}
}
</div>
);
}
and you will get the output.

How do I call an event handler or method in a child component from a parent?

I'm trying to implement something similar to the Floating Action Button (FAB) in the Material-UI docs:
https://material-ui.com/demos/buttons/#floating-action-buttons
They have something like:
<SwipeableViews>
<TabContainer dir={theme.direction}>Item One</TabContainer>
<TabContainer dir={theme.direction}>Item Two</TabContainer>
<TabContainer dir={theme.direction}>Item Three</TabContainer>
</SwipeableViews>
{
fabs.map((fab, index) => (
<Zoom>
<Fab>{fab.icon}</Fab>
</Zoom>
));
}
I have something like:
<SwipeableViews>
<TabContainer dir={theme.direction}>
<ListOfThingsComponent />
</TabContainer>
<TabContainer dir={theme.direction}>Item Two</TabContainer>
<TabContainer dir={theme.direction}>Item Three</TabContainer>
</SwipeableViews>
{
fabs.map((fab, index) => (
<Zoom>
<Fab onClick={ListOfThingsComponent.Add???}>
Add Item to List Component
</Fab>
</Zoom>
));
}
My ListOfThingsComponent originally had an Add button and it worked great. But I wanted to follow the FAB approach for it like they had in the docs. In order to do this, the Add button would then reside outside of the child component. So how do I get a button from the parent to call the Add method of the child component?
I'm not sure how to actually implement the Add Item to List click event handler given that my list component is inside the tab, while the FAB is outside the whole tab structure.
As far as I know I can either:
find a way to connect parent/child to pass the event handler through the levels (e.g. How to pass an event handler to a child component in React)
find a way to better compose components/hierarchy to put the responsibility at the right level (e.g. remove the component and put it in the same file with this in scope using function components?)
I've seen people use ref but that just feels hacky. I'd like to know how it should be done in React. It would be nice if the example went just a bit further and showed where the event handling should reside for the FABs.
thanks in advance, as always, I'll post what I end up doing
It depends on what you expect the clicks to do. Will they only change the state of the given item or will they perform changes outside of that hierarchy? Will a fab be present in every single Tab or you're not sure?
I would think in most cases you're better off doing what you were doing before. Write a CustomComponent for each Tab and have it handle the FAB by itself. The only case in which this could be a bad approach is if you know beforehand that the FAB's callback will make changes up and out of the CustomComponent hierarchy, because in that case you may end up with a callback mess in the long run (still, nothing that global state management couldn't fix).
Edit after your edit: Having a button call a function that is inside a child component is arguably impossible to do in React (without resorting to Refs or other mechanisms that avoid React entirely) because of its one-way data flow. That function has to be somewhere in common, in this case in the component that mounts the button and the ListOfThings component. The button would call that method which would change the state in the "Parent" component, and the new state gets passed to the ListOfThings component via props:
export default class Parent extends Component {
state = {
list: []
};
clickHandler = () => {
// Update state however you need
this.setState({
list: [...this.state.list, 'newItem']
});
}
render() {
return (
<div>
<SwipeableViews>
<TabContainer dir={theme.direction}>
<ListOfThingsComponent list={this.state.list /* Passing the state as prop */}/>
</TabContainer>
<TabContainer dir={theme.direction}>Item Two</TabContainer>
<TabContainer dir={theme.direction}>Item Three</TabContainer>
</SwipeableViews>
{
fabs.map((fab, index) => (
<Zoom>
<Fab onClick={this.clickHandler /* Passing the click callback */}>
Add Item to List Component
</Fab>
</Zoom>
))
}
</div>
)
}
}
If you truly need your hierarchy to stay like that, you have to use this method or some form of global state management that the ListOfThingsComponent can read from.

How to globally disable/hide/replace a component by name in React?

I have a large React app and I have a few components that I would like to completely disable from a config or global level. Is there any kind of global hook that I can use that is called before any component is rendered? If so, I imagine I can check the name of the component and return null if the name is on the disabled list. How would you do this?
There are a lot of ways to do this:
React's Context API allows you pass props through every level of the component tree so you can use them as flags to enable/disable components. Should be used sparingly however.
Higher Order Components are basically just functions that return a component. You could wrap your components in logic to render them as needed.
Or of course you could use a global state manager like redux to set global states.
There are many ways to do this, so, I'll just describe one simple way: using references and updating the states accordingly.
Full working feature hide/showing sandbox online: codesandbox.io ReactJS Feature Hide/Show Demo
Defined are two classes, class Feature extends React.Component and class App extends React.Component. The render() for <Feature/> is...
render() {
if (!this.state.enabled) {
return <div />;
}
return (
<div className="Feature">
<h1>My Feature!</h1>
</div>
);
}
And the option for enabling/disabling a feature in <App /> would handle display/hiding like so...
handleOnClick(e) {
if (e.target.checked) {
this.feature.setState({ enabled: true });
} else {
this.feature.setState({ enabled: false });
}
}
Of course, you need to make sure that <Feature /> has the reference set...
<Feature
ref={instance => {
this.feature = instance;
}}
/>
If you need simplest solution just use browser global vars and check it in render.
render() {
if( window.globalFlag ) return null
return (
<div> feature content...
Drawbacks:
modifying component,
using global scope,
some unnecessary code can be run earlier (f.e. constructor) and later (f.e. componentDidMount).
Use HOCs - wrap your component - connecting with global store using redux or context API.
<FlagsProvider store={flagStore}>
<SomeComponent_1>
<SomeComponent_2>
<FlagsConsumer flag="someFeatureFlag">
<SomeFeatureComponent />
<FlagsConsumer/> connects to store (redux connect would be an inner wrapper - composing HOCs) and conditionally renders <SomeFeatureComponent /> (or null).
Of course HOC can pass received props to wrapped component - it can be functionally transparent.
Don't reinvent the wheel - use some ready module, read tutorials, google for sth suitable.
HOC can also play a role of A/B testing.

Is render method of a component run in react-virtualized rowRenderer

I am using a List provided by react-virtualized. Inside the rowRenderer I return a more complex component. This component has some events defined for it. When an event is triggered, a part of the this component should be updated with new structure, e.g. a new input is rendered inside the row. This doesn't not seem to work when used inside the List.
<List height={height} width={1800} rowCount={this.state.leads.length} rowHeight={rowHeight} rowRenderer={this.rowRenderer} />
Here's the rowRenderer:
rowRenderer(props) {
let opacityvalue = 1
if (this.state.deleted.indexOf(props.index) >= 0) {
opacityvalue = 0.3
}
return (
<LeadItem {...props}
lead={this.state.leads[props.index]}
leadKey={props.index}
...
/>
)}
Here's the element that should show up when a specific event is triggered:
{self.props.lead.show ? <Selectize
queryfield={'tags'}
handleChange={(e) => this.props.updateLeadData(self.props.leadKey, 'tags', 'update', e)}
value={self.props.lead.tags}
create={false}
persist="false"
multiple
options={self.props.TAGS}
/> : <div>{taglist}</div>}
EDIT: Here's a simple example where I prove my point.
https://codesandbox.io/s/2o6v4my7pr
When user presses on the button, there must appear a new element inside the row.
UPDATE:
I see now that it's related to this:
https://github.com/bvaughn/react-virtualized#pure-components
I think you can achieve what you're trying to do by passing through this property to List as mentioned in the docs:
<List
{...otherListProps}
displayDiv={this.state.displayDiv}
/>

Resources