React/Redux - Update state in parent component from child component - javascript

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/

Related

Is there a way to export setState outside stateless components in react?

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)

React router doesn't update sub components params

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

React protected route

I have the following code :
const PrivateRoute = ({ component: Component, ...rest }) => {
return (<Route {...rest} render={(props) => (isAuthenticated() === true ? <Component {...props} /> : <Redirect to='/login' /> )} />)
}
but i dont understand how this works.. I call it like :
<PrivateRoute exact path = '/home' component={Home}></PrivateRoute>
it works , I just dont understand it. Where is props being passed in ? What is component: Component.
I would appreciate if someone could explain how this components works.
component: Component is object destructuring
You could re write it in the following way:
const PrivateRoute = (props) => {
const Component = props.component;
//shallow copy props, can also do {...props}
const rest = Object.assign({},props);
//delete component property from rest
// this will not affect props becaus of the shallow copy
The react-router Route component will take a props.component and renders that component with all props passed to Route minus the props.component.
You create a PrivateRoute that does the same but instead of rendering the component passed in props directly, it will render Route and pass a render prop to it that is a component created on the fly ()=>jsx. Route will render this component for you.

Passing props to a React Redux connected component wrapped in HOC

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;
}

How to connect component in html props in react-tippy to redux form

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
})

Categories