Implement Redux UI State Tree? - reactjs

I am trying to make all React Components' data to be available through Redux. I need it so that any component can easily modify / interact with other component.
But I don't know how to inform Redux about the fact that React Component was mounted / unmounted, because the component itself has no info about its place in the React tree. Let's say that we have a list of three React Components in our main App.js:
<div>
<ClickCounter />
<ClickCounter />
<ClickCounter />
</div>
ClickCounter is implemented here:
import React from 'react'
function ClickCounter() {
const clicks = useSelector(state => state.UI.ClickCounter[/* What is my id?*/].clicks)
return (
<div>
</div>
)
}
export default ClickCounter
This lonely ClickCounter function does not know whether it was called by the first, second or third <ClickCounter />. No info about the React tree from the component level - no synchronizing with Redux.
I stuck and don't know how to implement it. Thanks for your time in advance!

Related

Can not use component using hooks inside redux modal

I have a react redux app and the modal is managed through the redux store. So when user gets to the home page the <Home /> and <Modal /> mount at the root of the application. From there, whenever the user clicks a button that should open the modal, displayModal action is dispatched like so:
const handleModalButtonClick = () => {
this.props.actions.displayModal({
className: 'modal-light',
component: () => <FunctionalComponentThatUsesHooks />
});
};
The <FunctionalComponentThatUsesHooks /> is what is supposed to open up in the modal. But this doesn't seem to work when the component passed is a component using hooks as I am seeing the invalid hooks call error:
Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:...
The component using hooks is a function component, so I know that's not the issue. I can't seem to figure out exactly why this isn't working. Maybe has to do with the fact that the modal is managed through the redux store or the way component is being passed? Any ideas? Thanks
update: the displayModal action has type SHOW which just just adds the data to the redux store as so:
case SHOW:
return {
...state,
modal: action.payload
};
in the <Modal />, using function mapStateToProps to get the component from the redux store. then the <Modal /> render calls this.modalBody which will render that component from the redux store as so:
get modalBody() {
const {component: InnerComponent} = this.props;
if (!InnerComponent) {
return null;
}
return (
<div className="modal-body" ref={this.modalBodyNode}>
<InnerComponent {...this.props} />
</div>
);
}
the <FunctionalComponentThatUsesHooks /> isn't any specific component, I've tried with multiple different components that use hooks, even simple ones that only use the useState hook like so:
import React, {useState} from 'react';
const FunctionalComponentThatUsesHooks = (props) => {
const [size] = useState(props.size);
return (
<button className={`btn-${size}`}>{props.children}</button>
);
};
export default FunctionalComponentThatUsesHooks;

Can I put class base components inside function base components to use react hooks?

I have an app that was written mainly with class base react components, however we are implementing a paywall feature with Stripe.
The issue is stripe uses hooks, which don't work with class base components. Is it possible to put a child class base component into a parental function base component in order to achieve this. How would I go about doing this without rewriting every component as a function base component.
Every attempt to include a function base component with class base children always yields this error:
Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
Are there any work arounds, or should I re-write the all the components as functional components?
A quick example using react-router-dom for query parameters, then passing those into a class base child component.
function useQuery() {
return new URLSearchParams(useLocation().search);
}
const App = () => {
let query = useQuery();
return (
<Provider store={store}>
<Router>
<div className="App">
<Navbar />
<Switch>
{/* Public Pages */}
<Route exact path="/" component={Landing} ref={query.get('ref')} />
</Switch>
</div>
</Router>
</Provider>
)
}
export default App;
Then assuming that the landing components is a simple class base component that will set the prop into the state.
Component's type doesn't matter in relation parent-child. But you can't use hooks inside of classBased component. Only in functional ones
Here is the code example of Functional Component as Parent and Class as Child
import React , {Component}from "react";
import "./styles.css";
const App = () => {
return (
<div className="App">
<h1>I Am Parent Functional Component</h1>
<Child />
</div>
);
};
export default App;
class Child extends Component
{
render(){
return(
<h2>I am Class Child Component</h2>
)
}
}
Okay I figured out the issue with this problem. Since I found the same question unanswered on the internet, I decided to answer the question myself, Just for future reference for anyone looking for the same solution I was running into.
You can use Hooks on a parental Function Base Component and pass on the information into a child base component.
While rendering the application, I was given an error
Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
However this error was due to outdated modules in npm. I just updated the react-router-dom from 4.1 to 5.2.0 npm update react-router-dom and it seem to solve the issue of using functional base component as a parent.
After updating the module I was able to use a hooks in a parental function base component and pass that on to a child class base component using props.

Sending a React.FunctionComponent<React.SVGProps<SVGSVGElement>> as a prop to another component

I'm attempting to import a React functionComponent from an SVG and then send that to another component as a prop to render that svg. With the setup below, this compiles fine, but eventually crashes when trying to render the svg in browser with:
Error: Objects are not valid as a React child (found: object with keys {$$typeof, render}). If you meant to render a collection of children, use an array instead.
Classes below are simplified. But the gist of what I'm trying to do is:
In overlay.tsx:
import { ReactComponent as icon } from "/icon.svg";
import CustomItem from "/customItem";
const Overlay: React.FC<OverlayProps> = () => {
return (
<div>
<CustomItem icon={icon}/>
</div>
);
export default Overlay;
}
and in customItem.tsx:
import React from "react";
export interface CustomItemProps {
icon: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
}
const CustomItem: React.FC<CustomItemProps> = ({icon}) => {
return (
<div>
{icon}
</div>
);
};
export default ApplicationsDropdownItem;
I assume my problem is somewhere around the syntax of {icon}, but I can not for the life of me find out what I'm suppose to use instead.
Answer
The icon you are importing is a component, therefore it must be called to render the JSX.
<Icon {...props}/> (correct) or {Icon(props)} (not recomended)
Since it is a component, you should also name it Icon and not icon.
Take a look at this blog post that explains SVGR.
TL;DR - Best approach for rendering components
A. Call the component in your render method with component syntax <MyComponent/> not MyComponent().
B. Instantiate your component as a variable, and pass that to your render method's JSX block.
More info
#DustInCompetent brought to light the issue of calling a component as a function inside a JSX block.
As explained here and here, that will lead to react not registering a components hooks and lead to state and other problems.
If you are implementing a High Level Component (HOC), then you should not call a component within the render method (return statement in functional components), as this leads to problems for similar registration issues of the component.
import React from "react";
import { ReactComponent as SampleIcon } from "/sample_icon.svg";
export interface CustomItemProps {
Icon: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
}
const CustomItem: React.FC<CustomItemProps> = (props) => {
const Temp = props.Icon as React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
return (
<div>
<Temp/>
</div>
);
};
<CustomItem Icon={SampleIcon}/>
I think you should use <Icon /> instead of {icon} because it's a component.

where should I dispatch my redux

I am learning react and redux, and I just have a small question about where should I dispatch and share my redux store to react components, should I share and dispatch my store in whatever components that need the store or I should share and dispatch my store in one main component and share that values as props to order components?
for example I have these three components and I have my states stored in one FlashCard component and share that states to Cardlist component using props and then the CardList component will send that props to Card component. is it the right thing to do? and also in card component I use dispatch because it seem more convenient, but should I use dispatch in my main component FlashCard as well and pass the change to Card component? thanks for your help.
import React from 'react';
import CardList from './cardList';
import {connect} from 'react-redux';
const FlashCard =(props)=>{
return (
<div>
<CardList
cards={props.cards}
/>
</div>
)}
const mapStateToProps=(state)=>({
cards:state.cards
})
export default connect(mapStateToProps,null)(FlashCard)
and
import React from 'react';
import Card from './card';
const CardList =(props)=>{
const card=props.cards.map((c,i)=>(
<Card
key={i}
card={c}
/>
))
return(
<div>{card}</div>
)}
export default CardList
and a Card component to render all the cards
import React from 'react';
import {connect} from 'react-redux';
import { showCardInfo, hideCardInfo } from '../redux/flashCardRedux';
const Card =(props)=>{
const onCardClick=()=>{
console.log(props.card.id)
}
return (
<div>
{props.card.showInfo?
<div
onClick={()=>props.dispatch(hideCardInfo(props.card.id))}
>{props.card.name}</div>
:
<div
className='card'
onClick={()=>props.dispatch(showCardInfo(props.card.id))}
>
<div className='img'>
<img src={props.card.img}/>
<h3>{props.card.name}</h3>
</div>
</div>}
</div>
)}
export default connect()(Card)
For me, I have found it best practice to only refer to dispatch in the main component and only pass in what the child components require as properties. This not only means that you are not passing dispatch around to everything, but also allows for unit testing of the smaller components if required.
It also keeps the smaller components much "lighter" in that they only have what they need, and can easily then be rendered in other areas of your app.
In the future, if you ever wanted to swap out Redux for something similar, it means you are then only having to edit code in the main component rather than everywhere in your system.
Its always recommended to use dispatch in parent component because
child is also part of parent but as per requirement you can shift.
as you have parent to child connection either you can connect in
parent and pass data as `props` or either you can take out in child
component itself, it depend upon how complex your parent and
child.however for smaller component always use as props else go for
component wise connect.
for more info [follow this](https://reactjs.org/)

Pass router history to component for onClick

Trying to clean my React app up a little with Container and Presentation components. I'm using react-router v4 and I previously used an onClick event for each job to show a specific job page:
<div onClick={() => this.props.history.push('/jobs/' + job.slug)}>
//The job info
</div>
Now I've created a dumb component in a separate file for the jobs like this:
const Jobs = (props) => (
<div>
{
props.jobs.map(job =>
<div key={job.slug} onClick(//todo)>
<p>{job.title} at <Link to="/">{job.company}</Link></p>
</div>
)
}
</div>
)
but I can no longer access this.props.history
How can I solve this issue? Should I pass another prop in the following:
<Jobs jobs={jobs}/>
To access router objects(match, history and location) via props of the component, either the component has to be rendered via Route or it should wrap with withRouter higher-order component.
Check whether your Jobs component is directly rendered via Route with something like this.
<Route path="/jobs" component={Jobs} />
If it's not the case, and Jobs component is rendered with JSX inside the render method of another component, then wrap it with withRouter higher-order component as follows before you export it.
export default withRouter(Jobs);
This will ensure that Jobs component has access to all the router props.
Also, make sure that you use props.history instead of this.props.history since now it's functional component.

Resources