I have functional component wrapped with HOC. Its returns some props after api call. How do I set the state in my child component(functional).
const withEditHoc = (WrappedComponent, actioneffects) => {
class HOC extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
};
}
executeAllActions = async (data, id) => {
await Promise.all(data.map(act => this.props.dispatch(act(id)))).then(() =>
this.setState({ loading: false }),
);
};
componentDidMount = () => {
const editpageId = this.props.match.params.id;
this.executeAllActions(actioneffects, editpageId);
};
render() {
console.log(this.state.loading);
return (
<React.Fragment>
<Loading loading={this.state.loading}>
<WrappedComponent {...this.props} />
</Loading>
</React.Fragment>
);
}
}
return HOC;
This is my HOC Structure. After the api call the data will be in redux.
I am getting a prop for my functional component using mapToStateProp.(react version 16.3)
Please any suggestion for this.
Functional component
function ProjectDetails(props) {
const [projectValue, setValue] = useState({});
const [proData, setProData] = useState({ ...props.project });
useEffect(() => {
setProData({ props.project });//Here I need to set my data, Iam not able to set data here.
}, []);
return <div>{JSON.stringify(props.project)}</div>;
}
function mapStateToProps(state) {
return {
project: state.projects.project,
};
}
const projectDetailsWithHocLoading = withEditHoc(ProjectDetails, [actions.apiCall()]);
export default connect(mapStateToProps, null)(projectDetailsWithHocLoading);
I am a beginner to react. Please suggest a good way
mapStateToProps created for class components.
because you are using hooks, you should use useSelector hook
import { useSelector } from 'react-redux';
function ProjectDetails(props) {
const [projectValue, setValue] = useState({});
const proData = useSelector(state => state.projects.project)
return <div>{JSON.stringify(proData)}</div>;
}
const projectDetailsWithHocLoading = withEditHoc(ProjectDetails,actions.apiCall()]);
export default projectDetailsWithHocLoading;
Related
Next-Auth has the following example which is great for functions, however I have a class which I need to run const { data: session } = useSession() in it. I am wondering how can I convert it to make it valid in a class?
export default function AdminDashboard() {
const { data: session } = useSession()
// session is always non-null inside this page, all the way down the React tree.
return "Some super secret dashboard"
}
AdminDashboard.auth = true
I tried to add session: useSession() to the following constructor but it did not work.
My Class
export default class AdminDashboard extends React.Component {
constructor(props) {
super(props);
this.state = {
value: null,
areas:[],
areasid:[],
users: [],
isLoading: true,
isAreaLoading: true,
session: useSession() // THIS DID NOT WORK
};
this.checkAnswer = this.checkAnswer.bind(this);
}
}
AdminDashboard.auth = true
based on the answer below. I changed the script to be like this
const withSession = (Component) => (props) => {
const session = useSession()
// if the component has a render property, we are good
if (Component.prototype.render) {
return <Component session={session} {...props} />
}
// if the passed component is a function component, there is no need for this wrapper
throw new Error(
[
"You passed a function component, `withSession` is not needed.",
"You can `useSession` directly in your component.",
].join("\n")
)
}
export default class NewCampaign extends React.Component {
render(){
const { data: session, status } = this.props.session;
const { isLoading, users, areas, areasid, isAreaLoading } = this.state;
return (
<React.Fragment></React.Fragment>
)}
}
const ClassComponentWithSession = withSession(NewCampaign)
NewCampaign.auth = false;
NewCampaign.getLayout = function getLayout(page) {
return (
<Dashboard>
{page}
</Dashboard>
)
}
However, I am getting Cannot destructure property 'data' of 'this.props.session' as it is undefined.
You should use getSession and just await the result.
async function myFunction() {
const session = await getSession()
// session available here
}
You can use it both on client and the server.
If you want to use the useSession() hook in your class components you can do so with the help of a higher order component or with a render prop.
Higher Order Component
import { useSession } from "next-auth/react"
const withSession = (Component) => (props) => {
const session = useSession()
// if the component has a render property, we are good
if (Component.prototype.render) {
return <Component session={session} {...props} />
}
// if the passed component is a function component, there is no need for this wrapper
throw new Error(
[
"You passed a function component, `withSession` is not needed.",
"You can `useSession` directly in your component.",
].join("\n")
)
}
// Usage
class ClassComponent extends React.Component {
render() {
const { data: session, status } = this.props.session
return null
}
}
const ClassComponentWithSession = withSession(ClassComponent)
Render Prop
import { useSession } from "next-auth/react"
const UseSession = ({ children }) => {
const session = useSession()
return children(session)
}
// Usage
class ClassComponent extends React.Component {
render() {
return (
<UseSession>
{(session) => <pre>{JSON.stringify(session, null, 2)}</pre>}
</UseSession>
)
}
}
trying to share the states from one component to another: The state can be accessed from main component but it comes undefined when accessing from a new component
This is my reducer:
export const tableReducer=(state = [], action)=> {
switch (action.type) {
case 'SELECTED_LIST':
state = JSON.parse(JSON.stringify(action.payload));
return state;
default:
return state
}
}
access it from a different file:
const [userList, usersDispatch] = useReducer(tableReducer, []);
useEffect(() => {
const list = Object.keys(selectedRowIds).length > 0 ? selectedFlatRows.map(
d => d.original.email
)
: '';
usersDispatch({ type: 'SELECTED_LIST', payload: list, });
}, [selectedRowIds, selectedFlatRows]);
and in a new component:
const [userList] = useReducer(tableReducer);
const deleteUsers = () => {
console.log(userList)
}
but here console.log(userList) it results to undefined
For Sharing of state between components, you can use Context API with useReducer.
Context API provides a neat way of providing state to child components without ending up with a prop drilling situation. It requires that a Provider is setup, which provides its values to any of its Consumers. Any component that is a child of the Provider can consume the context.
First a piece of context is created.
CustomContext.js
import React from 'react';
const CustomContext = React.createContext();
export function useCustomContext() {
return React.useContext(CustomContext);
}
export default CustomContext;
We can define your reducer in a seperate file.
TableReducer.js
export const tableReducer=(state = [], action)=> {
switch (action.type) {
case 'SELECTED_LIST':
state = JSON.parse(JSON.stringify(action.payload));
return state;
default:
return state
}
}
next is to implement the provider, and give it a value within a "Parent" component (A higher up component)
Parent.js
import CustomContext from './CustomContext'
import { tableReducer } from './TableReducer'
const ParentComponent = () => {
const [userState, usersDispatch ] = React.useReducer(tableReducer, []);
const providerState = {
userState,
usersDispatch
}
return (
<CustomContext.Provider value={providerState} >
<ChildComponent /> //Any component within here can access value by using useCustomContext();
</CustomContext.Provider>
)
}
now any component nested within <CustomContext.Provider></CustomContext.Provider> can access whatever is passed into "value" prop of the Provider which is your context state and the dispatch method.
The child component will look like this (I have ommited your state values and such..)
Child.js
import { useCustomContext }from './CustomContext'
const ChildComponent = (props) => {
//your custom state variables and other methods
const { userState, usersDispatch } = useCustomContext();
useEffect(() => {
const list = Object.keys(selectedRowIds).length > 0 ? selectedFlatRows.map(
d => d.original.email
)
: '';
usersDispatch({ type: 'SELECTED_LIST', payload: list, });
}, [selectedRowIds, selectedFlatRows]);
return(
<div>your components dependent on selectedRowIds, selectedFlatRows<div>
)
}
You can't share the state with useReducer hook like you are trying to. Each call to useReducer returns a new state that is managed using the reducer function passed to useReducer hook.
Just as each call to useState returns a different state, each call to useReducer returns a different state. Two useReducer calls can't share the same state.
To share the state, you can use one of the following options:
Context API
React-Redux
Pass the state from parent component to child component using props
#Gandzal is correct but I found it was lacking a typscript version and also today createContext requieres a default parameter. This came up as one of the top answers on google so I thought I would share.
I setup my solution like this:
Custom context:
import React, {Dispatch} from 'react';
import {StateType, Action} from './reducer'
interface IContextProps {
state: StateType;
dispatch:Dispatch<Action>
}
const CustomContext = React.createContext({} as IContextProps);
export function useCustomContext() {
return React.useContext(CustomContext);
}
export default CustomContext;
Note StateType and Action:
export type StateType = {
items: Array<DataItems>;
date: Date;
};
export type Action = {
type: ActionKind;
payload: DataItems;
};
reducer:
export const reducer = (state: StateType, action: Action) => {
const { type, payload } = action;
let newArray: Array<DataItems> = [];
switch (type) {
case ActionKind.Checked:
newArray = state.items.map((item) => ({
...item,
checked: item.id === payload.id ? true : item.checked,
}));
return {
...state,
items: newArray,
}
default:
return state;
}
};
App.tsx:
import { reducer, initalState } from 'Shared/Reducer/reducer';
import CustomContext from 'Shared/Reducer/CustomContext';
const App: React.FC = () => {
const [state, dispatch] = React.useReducer(reducer, initalState);
const providerState = {
state,
dispatch,
};
return (
<CustomContext.Provider value={providerState}>
<main role="main">
// your components
</main>
</CustomContext.Provider>
);
};
export default App;
And one of your components:
import { useCustomContext } from 'Shared/Reducer/CustomContext';
export const MyComp: React.FC<MyType> = (props) => {
const { data} = props;
const { state, dispatch } = useCustomContext(); --- Your state and dispatch here
return (
<div>
// your component
</div >
);
}
New to React, trying to update the state which is initialized inside the new react context provider, after the API call is success. I am using React 16.3 .
Not able to update the state value, followed documented steps but still failed to achieve.
This is what I tried:
HTML:
<MyProvider>
<MyConsumer>
{context => (
{context.updateInitialData(this.props)}
)}
</MyConsumer>
</MyProvider>
js:
import React, { Component } from 'react';
const MyContext = React.createContext();
export const MyConsumer = HeaderContext.Consumer;
export class MyProvider extends Component {
state = {
data: null,
updateInitialData: this.updateInitialData
};
updateInitialData = () => {
this.setState({data: this.state.data})
}
render() {
return (
<MyContext.Provider
value={{
state: this.state,
updateInitialData: this.updateInitialData
}}
>
{this.props.children}
</MyContext.Provider>
);
}
}
The problem is that now even if you, correctly set the state using updateInitialData, you are actually calling the function in render which will then call setState triggering a re-render and continuing the cycle. What you need is instead to write the HOC and update the initialData in lifecycle method
import React, { Component } from 'react';
const MyContext = React.createContext();
export const MyConsumer = MyContext.Consumer;
export class MyProvider extends Component {
// you don't need to store handler in state since you are explicitly passing it as a context value
state = {
data: null
};
updateInitialData = (data) => { // getting data from passed value
this.setState({data: data})
}
render() {
return (
<MyContext.Provider
value={{
state: this.state,
updateInitialData: this.updateInitialData
}}
>
{this.props.children}
</MyContext.Provider>
);
}
}
HOC:
const withContext = (Component) => {
return class App extends React.Component {
render() {
return (
<MyConsumer>
{context => (<Component {...this.props} context={context} />)}
</MyConsumer>
)
}
}
}
and then you would use it like
class Consumer extends React.Component {
componenDidMount() {
this.props.context.updateInitialData(this.props.data);
}
render() {
}
}
export default withContext(Consumer);
and thne
<MyProvider>
<Consumer data={this.props}/>
</MyProvider>
I'm not sure whether you copy-pasted it wrong but you don't update your state with data provided to the handler:
updateInitialData = () => {
this.setState({data: this.state.data}) // ??? its doing nothing
}
try:
updateInitialData = (data) => {
this.setState({ data })
}
I am trying to call connect on a decorator that returns a react class
const SetLanguageFromPage = () => {
return WrappedComponent =>
class setLang extends React.Component {
static propTypes = {
pathContext: PropTypes.shape({
language: PropTypes.string.isRequired
})
};
componentDidMount() {
const currentLanguage = i18n.language;
const pageLanguage = this.props.pathContext.language;
// First request
if (!currentLanguage) {
i18n.language = pageLanguage;
}
// Only update on language change
if (currentLanguage !== pageLanguage) {
i18n.changeLanguage(pageLanguage);
}
}
render() {
return <WrappedComponent {...this.props} />;
}
};
};
const mapStateToProps = (state) => { return{...} }
const mapDispatchToProps = (dis) => { return{...} }
export default connect(...)(SetLanguageFromPage);
but when I then use the decorator on another react class I get this error...
Uncaught TypeError: Cannot call a class as a function
which I suppose is from connect changing my function to a react class. Is there any way to accomplish what I am trying to do? I would really like to be able to call actions to set the state from within this decorator, but I can't see how I can get at the store to call dispatch or map the dispatch to the props...
I am using https://www.gatsbyjs.org/ for this, so the general method has the store instantiated in a way where I cannot access is directly
You get an error, because you are trying to pass and HOC to connect, whereas it expects a React component. You can instead connect the returned component inside the HOC, which is what you essentially want to do
const SetLanguageFromPage = () => {
return WrappedComponent => {
class SetLang extends React.Component {
static propTypes = {
pathContext: PropTypes.shape({
language: PropTypes.string.isRequired
})
};
componentDidMount() {
const currentLanguage = i18n.language;
const pageLanguage = this.props.pathContext.language;
// First request
if (!currentLanguage) {
i18n.language = pageLanguage;
}
// Only update on language change
if (currentLanguage !== pageLanguage) {
i18n.changeLanguage(pageLanguage);
}
}
render() {
return <WrappedComponent {...this.props} />;
}
};
return connect(mapStateToProps, mapDispatchToProps)(SetLang);
}
};
const mapStateToProps = (state) => { return{...} }
const mapDispatchToProps = (dis) => { return{...} }
export default SetLanguageFromPage;
I'm using react-lifecycle-component in my react app, and incurred in this situation where I need the componentDidMount callback to load some data from the backend. To know what to load I need the props, and I can't find a way to retrieve them.
here's my container component:
import { connectWithLifecycle } from "react-lifecycle-component";
import inspect from "../../../libs/inspect";
import fetchItem from "../actions/itemActions";
import ItemDetails from "../components/ItemDetails";
const componentDidMount = () => {
return fetchItem(props.match.params.number);
};
// Which part of the Redux global state does our component want to receive as props?
const mapStateToProps = (state, props) => {
return {
item: state.item,
user_location: state.user_location
};
};
// const actions = Object.assign(locationActions, lifecycleMethods);
export default connectWithLifecycle(mapStateToProps, { componentDidMount })(
ItemDetails
);
Any clues?
thanks.
import React, { Component } from 'react'
import { connect } from 'react-redux'
import fetchItem from '../actions/itemActions'
class Container extends Component {
state = {
items: []
}
componentDidMount() {
const { match } = this.props
fetchItem(match.params.number)
// if your fetchItem returns a promise
.then(response => this.setState({items: response.items}))
}
render() {
const { items } = this.state
return (
<div>
{ items.length === 0 ? <h2>Loading Items</h2> :
items.map((item, i) => (
<ul key={i}>item</ul>
))
}
</div>
)
}
const mapStateToProps = (state, props) => {
return {
item: state.item,
user_location: state.user_location
}
}
export default connect(mapStateToProps)(Container)
Though I don't see where you are using the props you take from your Redux store...