Getting props in a sub function when using a HOC - javascript

I am trying to make a HOC that handles some functionalities in react and i am creating my HOC this wat
import React, { useState } from "react";
export default function withFormControl(WrappedComponent) {
return props => {
const onChangeField = e =>
setFields({ ...fields, [e.target.id]: e.target.value });
const [fields, setFields] = useState({});
const submitForm = e => {
console.log(fields);
e.preventDefault();
};
return (
<WrappedComponent
onChangeField={onChangeField}
submitForm={submitForm}
fields={fields}
{...props}
/>
);
};
}
And i am using it in my component like this:
import React, { useState } from "react";
import { Col, Form, FormGroup, Label, Input, Button } from "reactstrap";
import { Typo, Ico, AuthLayout } from "../../../components";
import "./Applicant.css";
import FormInput from "../../../components/common/FormInput/FormInput";
import withFormControl from "../../../hocs/WithFormControl";
function Applicant(props) {
console.log(props); // I get the props here
const subFunction = props => {
console.log(props); // returns undefined
}
}
export default withFormControl(Applicant);
I cant get the props in the inner function, any reason for this behavior?

Problem is that param of function called subFunction is shadowing variable from outer scope and then subFunction is called without any params.

Related

How to prevent unnecessary re-renders when using context in React?

In my app, I want to pass the global store of my application to the child components via context. For the sake of an example, I have created 2 child components namely Child1 and Child2, and passing increment and decrement counter functions along with the corresponding counter values to them. Child1 is responsible for incrementing counter1 and child2 for decrementing counter2. When I am invoking the increment/decrement function in the components, the other component is uselessly getting re-rendered along with the parent. How can I prevent this from happening?
Please find the above use-case here
Below is the code for the same,
App.js
import React, { useState, useCallback } from 'react';
import './style.css';
import { UserContext } from './Context.js';
import Child1 from './Child1';
import Child2 from './Child2';
export default function App() {
const [counter1, setCounter1] = useState(0);
const [counter2, setCounter2] = useState(0);
const IncrementCounter = useCallback(
() => setCounter1((prevState) => prevState + 1),
[setCounter1]
);
const DecrementCounter = useCallback(
() => setCounter2((prevState) => prevState - 1),
[setCounter2]
);
const store = {
child1: {
counter1,
IncrementCounter,
},
child2: {
counter2,
DecrementCounter,
},
};
console.log('App re-rendering');
return (
<UserContext.Provider value={store}>
<Child1 />
<Child2 />
</UserContext.Provider>
);
}
Child1.js
import React, { useContext } from 'react';
import { UserContext } from './Context.js';
const Child1 = () => {
const store = useContext(UserContext);
const { child1 } = store;
console.log('Child1 Re-rendering');
return (
<div>
<p>{`Counter1 value : ${child1.counter1}`}</p>
<button onClick={child1.IncrementCounter}>Increment</button>
</div>
);
};
export default React.memo(Child1);
Child2.js
import React, { useContext } from 'react';
import { UserContext } from './Context.js';
const Child2 = () => {
const store = useContext(UserContext);
const { child2 } = store;
console.log('Child2 Re-rendering');
return (
<div>
<p>{`Counter2 value : ${child2.counter2}`}</p>
<button onClick={child2.DecrementCounter}>Decrement</button>
</div>
);
};
export default React.memo(Child2);
Context.js
import React from 'react';
const UserContext = React.createContext();
export { UserContext };

React Redux & ContextApi - How to pass a prop via context and keeping it "connected"?

Considering the following project setup on a react-redux application that uses context API to avoid prop drilling. The example given is simplified.
Project Setup
React project uses React Redux
Uses context API to avoid prop drilling in certain cases.
Redux store has a prop posts which contains list of posts
An action creator deletePost(), which deletes a certain post by post id.
To avoid prop drilling, both posts and deletePosts() is added to a context AppContext and returned by a hook funciton useApp().
posts array is passed via contexts so it is not used by connect() function. Important
Problem:
When action is dispatched store is updated however Component is not re-rendered (because the prop is not connected?). Of course, if I pass the prop with connect function and drill it down to child rendering works fine.
What is the solution?
Example Project
The example project can be found in codesandbox. Open up the console and try to click the delete button. You will see no change in the UI while you can see the state is updated in the console.
Codes
App.js
import Home from "./routes/Home";
import "./styles.css";
import { AppProvider } from "./context";
export default function App() {
return (
<AppProvider>
<div className="App">
<Home />
</div>
</AppProvider>
);
}
context.js
import { useDispatch, useStore } from "react-redux";
import { useContext, createContext } from "react";
import { deletePost } from "./redux/actions/posts";
export const AppContext = createContext();
export const useApp = () => {
return useContext(AppContext);
};
export const AppProvider = ({ children }) => {
const dispatch = useDispatch();
const {
posts: { items: posts }
} = useStore().getState();
const value = {
// props
posts,
// actions
deletePost,
dispatch
};
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
};
Home.js
import { connect } from "react-redux";
import Post from "../components/Post";
import { useApp } from "../context";
const Home = () => {
const { posts } = useApp();
return (
<section>
{posts.map((p) => (
<Post key={p.id} {...p} />
))}
</section>
);
};
/*
const mapProps = ({ posts: { items: posts } }) => {
return {
posts
};
};
*/
export default connect()(Home);
Post.js
import { useApp } from "../context";
const Post = ({ title, content, id }) => {
const { deletePost, dispatch } = useApp();
const onDeleteClick = () => {
console.log("delete it", id);
dispatch(deletePost(id));
};
return (
<article>
<h1>{title}</h1>
<p>{content}</p>
<div className="toolbar">
<button onClick={onDeleteClick}>Delete</button>
</div>
</article>
);
};
export default Post;
You're not using the connect higher order component method properly . Try using it like this so your component will get the states and the function of your redux store :
import React from 'react';
import { connect } from 'react-redux';
import { callAction } from '../redux/actions.js';
const Home = (props) => {
return (
<div> {JSON.stringify(props)} </div>
)
}
const mapState = (state) => {
name : state.name // name is in intialState
}
const mapDispatch = (dispatch) => {
callAction : () => dispatch(callAction()) // callAction is a redux action
//and should be imported in the component also
}
export default connect(mapState , mapDispatch)(Home);
You can access the states and the actions from your redux store via component props.
Use useSelector() instead of useState(). Example codepen is fixed.
Change from:
const { posts: { items: posts } } = useStore().getState();
Change to:
const posts = useSelector(state => state.posts.items);
useStore() value is only received when component is first mounted. While useSlector() will get value when value is changed.

React Context is not defined no-undef

I have navigation constant which is an array of objects(web-store mega-nav). I need to use context provider and when I'm trying to use my context it's telling me NavContext' is not defined no-undef.
NavContext.js
import { createContext } from 'react'
const navigation = [...] // array of objects
const NavContext = createContext(navigation)
export default NavContext
Nav.js
import {createContext} from 'react'
import NavContext from './context/NavContext' //added
function Nav() {
return (
<NavContext.Provider> //deleted value
// childrens
</NavContext.Provider>
)
}
Sidebar.js
//then in one of the child I'm trying to call it:
import { useContext } from 'react'
import NavContext from '../context/NavContext' //added
function Sidebar(){
const nav = useContext(NavContext)
return (
{nav.map(...)} // nav is undefined
)
}
Now nav constant is undefined when I'm using useContext
You need to export the create context like this
export const NavContext = createContext(navigation)
Then import it into your child component like this
import { NavContext} from "../Nav";
//Create a new NavContext.js File.
import React, { createContext, useReducer } from "react";
export const NavContext = createContext();
const initialState = {
}
function reducer(state, action) {
return { ...state, ...action };
}
export const NavProvider = (props) => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<NavContext.Provider value={{ state, dispatch }}>
{props.children}
</NavContext.Provider>
);
};
Then in your index.js file.
import { NavProvider} from "./NavContext";
ReactDOM.render(
<NavProvider>
<App />
</NavProvider>,
document.getElementById("root")
);
If that doesnt work idk what will.
Try not to pass any arguments into createContext, then pass navigation into Context provider as prop
<Context.Provider value={navigation} />
And then get the value using useContext Hook in your consumer component
For my knowledge, you have to import useContext like this.
import React, { useContext } from 'react'

How can I expose a value to the global actions to share it among components?

I am trying to expose a value in order to share it among components:
I have this reducer:
import createReducer from '../../../redux/createReducer';
import ActionTypes from '../constants/ActionTypes';
const initialState = {
currentService: 'otherservices',
};
export const handlers = {
[ActionTypes.SELECTED_SERVICE_ACTION](state, action) {
return {
...state,
currentService: action.payload,
};
},
};
export default createReducer(initialState, handlers);
And this action:
import ActionTypes from '../constants/ActionTypes';
export const selectedServiceAction = service => ({
type: ActionTypes.SELECTED_SERVICE_ACTION,
payload: service,
});
export default selectedServiceAction;
And I have this component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { translate } from 'react-i18next';
import { DropdownV2 } from 'carbon-components-react';
import PropTypes from 'prop-types';
import TableMultiSelectItems from './TableMultiSelectItems';
import { selectedServiceAction } from '../actions/cancellations';
class TableMultiselect extends Component {
constructor(props) {
super(props);
this.state = {
itemState: 'otherservices',
};
}
onChange = e => this.setState({ itemState: e.selectedItem.id });
render() {
const { t } = this.props;
// Array of dropdown's items
const menuItems = TableMultiSelectItems(t);
return (
<div>
<DropdownV2
items={menuItems}
onChange={this.onChange}
/>
</div>
);
}
}
const wrappedComponent = connect(
() => ({
serviceSelected: this.state.itemState,
}),
dispatch => ({
serviceSelectedHandler: serviceSelected => {
dispatch(selectedServiceAction(serviceSelected));
},
}),
)(TableMultiselect);
TableMultiselect.propTypes = {
t: PropTypes.func.isRequired,
serviceSelectedHandler: PropTypes.func.isRequired,
};
export default translate()(wrappedComponent);
What I need from the component above is to take the value returned by the onChange function (itemState: e.selectedItem.id });) and expose it in order to grab it in another component and do something like this:
//other imports
import the-Action-To-Get-The-OnChange-Value from "..."
const Cancellations = () => (
<React.Fragment>
{the-Action-To-Get-The-OnChange-Value.id === 'thisID' ?
<SLRequests/> : <SLDevices/>
}
</React.Fragment>
);
This is the first component I am trying to do with Redux, so I need some help trying to achieve what I need.
If you want something to be accessible globally, you will have to store it inside application state (redux store). In your particular case, you want to store e.selectedItem.id. So besides setting the state (however this is redundant because the variable will be accessible globally).
What you need to do? Inside onChange function you have to dispatch the action and pass the argument.
onChange = (e) => this.props.dispatch(selectedServiceAction(e.selectedItem.id));
Note: To access the dispatch function your component has to be connected.
Then it will be catched by reducer and will be saved in store.

React-Redux, when dispatching a function, "uncaught TypeError is not a function"

I'm having an issue where when I want to dispatch an action, fetchRewardByPromoCodeAction it's saying that the action I want to dispatch is not a function.
In the the form, I use the the event handleer onSubmit then use handleSubmit. I noticed that my props becomes undefined so, which leads me to thinking that the connect function isn't working as expected. Any assistance would be helpful. Here's the code.
import React, { Component } from 'react';
import { connect
} from 'react-redux';
import PromoCodeInput from 'components/promoCodeForm/PromoCodeInput';
import { fetchRewardByPromoCode, resetValidations } from 'rewards/ducks';
import Button from 'button/Button';
export class AdminRewardPage extends Component<Props> {
constructor(props) {
super(props);
this.state = {
promoCodeText: '',
};
}
onPromoCodeChange = (event) => {
this.setState({
promoCodeText: event.target.value,
});
const { resetValidationsAction } = this.props;
resetValidationsAction();
};
handleSubmit = (event) => {
event.preventDefault()
const { fetchRewardByPromoCodeAction } = this.props;
const { promoCodeText } = this.state;
fetchRewardByPromoCodeAction(promoCodeText);
}
render() {
const { promoCodeText } = this.state
return (
<div>
<h1>AdminRewardPage</h1>
<form onSubmit={this.handleSubmit}>
<PromoCodeInput inputValue={promoCodeText} onChangeHandler={this.onPromoCodeChange} />
<Button type="submit" label="Find By PromoCode" fullWidth />
</form>
</div>
)
}
}
const mapDispatchToProps = (dispatch) => ({
resetValidationsAction: () => dispatch(resetValidations()),
fetchRewardByPromoCodeAction: (promoCodeText) => dispatch(fetchRewardByPromoCode(promoCodeText)),
});
export default connect(null, mapDispatchToProps)(AdminRewardPage);
in rewards/ducks.js
export const fetchRewardByPromoCode = (promoCode: string): FSAModel => ({
type: FETCH_REWARD_BY_PROMOCODE,
payload: promoCode,
})
---EDIT--WITH--ANSWER---
#Bartek Fryzowicz below helped lead me to right direction. I forgot to look in my index.js file where my routes are
Previously I had
import { AdminRewardPage } from 'scenes/AdminRewardPage'
instead of
import AdminRewardPage from 'scenes/AdminRewardPage'
<Router history={ history }>
<Switch>
<Route exact path={ `/rewards` } component={AdminRewardPage} />
</Switch>
</Router>
I didn't bother to look how I was importing it.
LESSON
Look at where and HOW your files are being imported and exported.
You're trying to call fetchRewardByPromoCode function inside mapDispatchToProps but such function (fetchRewardByPromoCode) is not declared inside mapDispatchToProps scope nor in parent scope. Maybe you have forgotten to import it?
Answer update:
Please make sure that when you use the component you use default export (not named export) since named export is the presentational component not connected to redux store. You have to use container component connected to redux so make sure you import it like this:
import AdminRewardPage from '/somePath'
not
import { AdminRewardPage } from '/somePath'

Categories