I am using a HOC to wrap all my components with the Redux Provider. But when using the components individually I sometimes need to pass props to them and using ownProps gives me the props from the HOC wrapper.
How can I pass props directly to the connected component?
I am essentially sprinkling React to an existing site, so I need to render my components to the DOM using HTML elements as containers. I want all the containers to be connected to the store but I also want to pass data directly to the HTML elements on the page and use that data in the component.
// HOC component 'withStore.js'
export default function (WrappedComponent) {
function ReduxStore() {
return (
<Provider store={ store }>
<WrappedComponent testProp="Dont show me!" />
</Provider>
);
}
return ReduxStore;
}
// The connected component I want to pass props directly to
export class ListingThumbnails extends PureComponent {
render() {
const {
ownProps,
} = this.props;
console.log(this.props.ownProps);
}
}
const mapStateToProps = (state, ownProps) => ({
state,
ownProps,
});
export default withStore(connect(mapStateToProps)(ListingThumbnails));
// Rendering to the DOM
ReactDOM.render(
<ListingThumbnailsComponent testProp="Show me!" />,
document.querySelector('#listing-thumbnails-container'),
);
The console log is displaying the props passed to the WrappedComponent
But I want it to show the props passed to the ListingThumbnailsComponent
I'm not sure to get your problem, but your ReduxStore is a component now. It's a component around your "wrapped component". It means that you have to pass the props of the ReduxStore to its child to console log the props at a deeper level:
export default function (WrappedComponent) {
function ReduxStore(props) {
return (
<Provider store={ store }>
<WrappedComponent {...props} />
</Provider>
);
}
return ReduxStore;
}
Related
Consider this example
export function InsideHoc(props){
const [A, setA] = useState(false);
return({if(A) && (<h1>Print something</h1>)});
}
In another file
import {A, setA} from './inside-file';
function ToggleFromOutside(){
return(<p onClick={setA(!A)}>Verify</p>);
}
Can setA be exposed outside so that the state of this component be changed from outside? I know this can be done through redux. But without using this, is there a way to change the state of one component?
Structure is like this
import {withCreateHOC} from './external';
import childComponent from './child';
class A extends React.Component {
render(){
<Another menus={(item) => <MenuItems object={item} />}
/>
}
}
export default withCreateHOC(A, {custom: childComponent, title: 'Add'});
//withCreateHOC renders modal here as well as it has a button to toggle the state. Same state should be used from below function
function MenuItems(){
return(<button onClick={displayModal}>)
}
Yes.
You can lift the state in that case. This works good if you don't need to pass down the setState to far down the tree.
If you don't want to pass the setState function all the way down the React element tree, you will need to use context, redux or some other state handling.
Example state lift
export function Parent(){
const [message, setMessage] = useState("Hello World");
return (
<>
<Child1 message={message} />
<Child2 changeMessage={setMessage} />
</>
);
}
// Can be in other file
function Child1(props){
return(<p>{props.message}</p>);
}
// Can be in other file
function Child2(props){
return(
<a onClick={() => props.changeMessage("Changed")}>
I can change things in other components.
</a>
);
}
Example of React tree with shared context/redux
<WithRedux>
<App>
<Navigation />
<Modal />
<PageRenderer />
<SomeList>
<ListItem />
<ListItem />
</Somelist>
</App>
<WithRedux>
All the children of the WithRedux component can access the state and modify it.
(PS: You can wrap the App with the HOC withRedux etc, this example is just for visualization)
In my React application, I have trouble making a sub-component update based on props.
the sub-component gets the props from a <Link/> tag that is exposed to store state
const CallPortfolioManagement= (props) => {
const { portfolio } = props;
return (
<div>
<Link
to={{pathname: `/portfolios/${portfolio.name}`,state: { portfolio: portfolio},}}>
{portfolio.name}</Link>
</div>
);
};
const mapStateToProps = (state) => {
return {
portfolio: getPortfolio(state),
};
};
export default connect(mapStateToProps)(CallPortfolioManagemnt);
the PortfolioManagement component is:
const PortfolioManagement = (props) => {
const portfolio = useLocation().state.portfolio;
return (
<>
{portfolio.stocks.map((stock, index) => (
<div key={stock.symbol}>
<h1>
{stock.symbol}
</h1>
</div>
))}
</>
);
};
export default PortfolioManagement;
a component that got a direct subscription to the state and rerenders when a new stock symbol is added:
const RenderLastStock= (props) => {
const renderLast () => {
var stocks;
if (props.portfolio) {
stocks = props.portfolio["stocks"];
return <button>{stocks[stocks.length - 1]].symbol}</button>;
}
};
return (
<>
renderLast ()}
</>
);
};
const mapStateToProps = (state) => {
return { tasks: getLoadingTasks(state), portfolios: getPortfolios(state) };
};
export default connect(mapStateToProps)(RenderLastStock);
the route declared here and calls PortfolioManagement when clicked:
function App(props) {
useEffect(() => {
props.getPortfolios();
}, []);
return (
<Router>
<div className="App">
<Switch>
<PrivateRoute>
<Route path="/portfolios/:id" component={PortfolioManagement} />
</PrivateRoute>
</Switch> </div>
</Router>
);
}
the problem is that PortfolioManagement gets the params but does no rerender when the state is changed - when I add stock symbols.
I update the store's state with Object.assign and other components that are subscribed to this state do rerender! (so there aren't any immutability problems)
looking in the redux devtools I can see the state is updated correctly, I suspect that PortfolioManagement does not rerender because react does not refer to Link's Params as props and does not know it should trigger a rerender.
please help:(
instead of using useLocation, you can use withRouter at PortfolioManagement -
import { withRouter } from 'react-router-dom'
const PortfolioManagement = (props) => {
console.log(props.location && props.location.state)
...rest code...
}
export default withRouter(PortfolioManagement);
I know its hacky, but anyhow now state comes from props and component will re-render
Edit
The usage of Link and the state location object you can send with, works on a way that the context won't be exist if the component wasn't called through the link, consider send the props through regular props at Router decoration (that I assuming is a component connected to redux store)
<Route path="/portfolios/:id" render={()=> <PortfolioManagement props={...props} />} />
didn't find a solution with react router, I solved it by cheating and giving portfolioManagement direct access to the store
I've 2 components: Header and Child in an already developed react application that uses redux-saga.
Header component has 2 material-ui Select component. Now when I route to child component I want to disable those 2 Select component in Header by updating it's state.
App.js
const App = ({ store }) => {
return (
<ThemeProvider theme={theme}>
<Provider store={store}>
<Header />
<Router />
<Footer />
</Provider>
</ThemeProvider>
);
};
Router.js
<Route
exact
path={`${url}BatchFileRawDataDashboard`}
component={Child}
/>
Header.js
<Select
style={{ width: 112 }}
value={this.state.selectedCycle}
autoWidth={true}
disabled={this.disableCycle.bind(this)}
>
{cycleDateItem}
</Select>
Header component has no props and I am very much confused with how mapStateToProps and mapDispatchToProps works.
How to update state of parent component from child component that uses redux?
mapStateToProps = putting redux store state into the props
mapDispatchToProps = putting redux actions into the props
So on the child, you want to call an action that will update the store via mapDispatchToProps
Then on the parent you want to use mapStateToProps to read this updated value
// PARENT READ DATA
const mapStateToProps = (store) => ({
parentData: store.parentData,
})
// CHILD ACTION TO UPDATE STORE
// the function here triggers a action which triggers a reducer
const mapDispatchToProps = dispatch => ({
updateParentAction: data => dispatch(some_action_name(data)),
})
I suggest reading up on how redux works, it's simple once you get it, but complicated to start with https://www.valentinog.com/blog/redux/
I'm building a webpage and realized a common style shared by each component (same background, border, and title style). So I thought I should make an HOC which accepts the inner content of each component as well as a title, and returns an outer component which wraps this inner component and heading.
At first I ran into a lot of issues trying to get this to work, being new to React, but now it's finally working but I still don't understand how.
Here is my HOC
const BaseBlock = (WrappedComponent) => {
return class BaseBlock extends Component {
render () {
return (
<div className={styles['base-block']}>
<div className={styles['container']}>
<div className={styles['base-block-head']}>
{ this.props.title }
</div>
<div className={styles['base-block-body']}>
<WrappedComponent {...this.props} />
</div>
</div>
</div>
)
}
}
}
export default BaseBlock
This is the WrappedComponent:
const HighlightsBlock = (props) => {
return <ListsComponent items={props.items} />
}
export default BaseBlock(HighlightsBlock)
And this is the ListsComponent
const ListsComponent = (props) => {
if (props.items) {
return (
<ul className={styles['styled-list']}>
{props.items.map((item, idx) => {
return (
<li key={idx} className={styles['styled-list-item']}>{item}</li>
)
})}
</ul>
)
} else return (
<h3>No highlights</h3>
)
}
export default ListsComponent
And this is how I'm using the component in my app:
<HighlightsBlock items={this.getHighlights()} title='Highlights' />
Now, I can see the HighlightsBlock component receiving props twice (Once when I'm using it in my App with props, and once inside the HOC Baseblock as WrappedComponent ). If I remove props from either of these places it stops working. I don't understand how this is working.
When you render <HighlightsBlock items={this.getHighlights()} title='Highlights' /> you are actually rendering the component returned by HOC which in turn renders your actually HighlightsBlock component as <WrappedComponent {...this.props} />
You can think of HighlightsBlock component to be nested two level deep and hence you need to pass on the props to it, firstly as {...this.props} from within HOC and then receive it as props in functional component
This is because of this.getHighlights() in this line,
<HighlightsBlock items={this.getHighlights()} title='Highlights' />
Every time you pass props to child component this function is getting executed.
To solve this issue, maintain a state value in your parent component and set that value in getHighlights function like,
getHighlights(){
//you logic to get data
this.setState({items:data.items}); //considering `data` is object which has `items`
}
Now you can pass items like,
<HighlightsBlock items={this.state.items} title='Highlights' />
I have got a problem with react-tippy component. I would like to pass in html props react component with redux form, but I receive an error like this:
Uncaught Error: Could not find "store" in either the context or props of "Connect(Form(AssignDriverForm))". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(Form(AssignDriverForm))".
Here is code of react tippy component:
class AssignDriverToolTip extends Component {
state = { open: false }
setIsOpen = () => this.setState({ open: true });
setIsClose = () => this.setState({ open: false });
render() {
return (
<CustomTooltip
theme="light"
open={this.state.open}
arrow={false}
html={(
<AssignDriverForm/>
)}
position='right'
trigger="click" >
<CustomButton infoSmallWithIcon onClickHandler={() => this.setIsOpen()}>
<SVGComponent icon="pen" color="white" width="12px" height="12px" />
{strings.assignDriver}
</CustomButton>
</CustomTooltip>
)
}
}
export default AssignDriverToolTip;
And also here is an AssignDriverForm component:
class AssignDriverForm extends Component {
handleSubmit = (data) => {
console.log(data);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<Field
name="message"
type="textarea"
autoComplete="off"
component="textarea" />
</form>
)
}
}
AssignDriverForm = reduxForm({
form: 'assignDriver'
})(AssignDriverForm)
export default AssignDriverForm;
When I change it to component without redux-form everything works fine. But I really dont know how to fix it. Could you help me ?
As the error message says: find the root component where you create your store. (Something like this in index.jx)
const store = createStore(reducer)
Then make sure you wrap your component in the <Provider> wrapper.
// render app
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app')
)
Redux form uses a normal Redux store, so somewhere you need to add that reducer to your main reducer. You may have something like this:
// combine reducers
const reducer = combineReducers({
myReducer,
...
form: formReducer // for redux-form
})