I have read lots of similar questions,And no one fit for me.
about
this.props.children doesn't always inherit the context from its parent
Could not find "store" in either the context or props
//store config
const createHistoryWithBasename = (historyOptions) => {
return useBasename(createHistory)({
basename: '/user_home'
})
}
const finalCreateStore = compose(
applyMiddleware(thunk, createLogger()),
reduxReactRouter({
createHistory: createHistoryWithBasename }),
devTools()
)(createStore);
export function configureStore(initialState) {
const store = finalCreateStore(rootReducer, initialState);
return store;
}
the index file, all route nested in Provider.
//entry file
let routes = (
<Route path="/" component={UserHome}></Route>
);
ReactDom.render(
<Provider store={store} >
<ReduxRouter>
{routes}
</ReduxRouter>
</Provider>,
rootElement
);
the Component. I logged the this.props, there are't the dispatch property. And the parameter of context is an empty object.
//user_home.js
class UserHome extends React.Component {
constructor(props, context){
//context is empty Object
super(props, context);
}
render() {
//this.props.dispatch is undefined
return (
<div className="container">
test
</div>
);
}
}
function mapStateToProps(state){
return state;
}
export default connect(mapStateToProps, {
pushState
})(UserHome);
Maybe, it's a simple question, but It took me a lot of time.Hope to get your help. Thank you!
Maybe we can inject the action creator to connect
import * as ActionCreators from './actions/user_actions';
//we can use the actions in ActionCreators to dispatch the action.
export default connect(mapStateToProps, {
pushState: pushState,
...ActionCreators
})(Component);
the actions
//action file
export function xxAction(){
return dispatch =>{
dispatch({
type: 'XX_ACTION'
});
}
}
this solution fit for dispatching actions in children component.There are some limitations, Looking forward to a better solution.
Related
I've connected presentational-functional component to the redux store by means of connect function.
export default connect(
state => ({tasks: state.tasks}),
dispatch => ({
getTasks: () => apiGetTasks(dispatch),
getTask: (id) => apiGetTask(dispatch, id),
})
) (TaskView)
And now I want to redirect on the other page when getTask has triggered (or even better when apiGetTask has finished). So I tried to use history in the next way:
export default connect(
state => ({tasks: state.tasks}),
dispatch => ({
getTasks: () => apiGetTasks(dispatch),
getTask: (id) => apiGetTask(() => {
const history = useHistory()
dispatch(id)
history.push('/otherPage')
}, id),
})
) (TaskView)
But it does not work with the message:
Unhandled Rejection (Error): Invalid hook call. Hooks can only be called inside of the body of a function component....
I see that it is wrong pattern probably... So, what is the best one? Do I need to pass callback from parent component and make redirect in it? Or may be I should redirect in presentational component (but it looks strange for presentational component)
Or may be I should call dispatch(switchToPage()) in apiGetTask's promise and implement redirection in the application component, basing on the value?
What is the best solution in such case?
And one related question yet: Is it regular to use api calls in such manner as above?
Thank you, in advance!
Well.
Now you can assess my solution:
---index.js----
export let history = null;
const DefaultComponent = ()=> {
history = useHistory()
return <div style={{visibility: 'hidden'}}/>
}
ReactDOM.render(
<React.StrictMode>
<Provider store={createAppStore()}>
<Router>
<DefaultComponent/>
<Dashboard />
</Router>
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
---Tasks.js----
import {history} from './index.js'
export default connect(
state => ({tasks: state.tasks}),
dispatch => ({
getTasks: () => apiGetTasks(dispatch),
getTask: (id) => apiGetTask(dispatch, id, ()=>history.push('/CreateTask')),
})
) (TaskView)
Don't hesitate to criticise. Any proposals are welcome!
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'm struggling to pass some data from one component to another one using <Link />. I cannot pass any params to my URL so, please excuse that solution. Below are my sample files from my problem,
App.jsx
<BrowserRouter>
<Router history={history}>
<Route exact path={'/my-dashboard/first-page'} component={FirstComponent}/>
</Router>
</BrowserRouter>
LinkingComponent.jsx
const LinkingComponent = ({ data }) => {
return (
<Link
to={{
pathname: '/my-dashboard/first-page',
state: { userDetails: data }
}}
>
{data.firstName}
{data.lastName}
</Link>
);
};
FirstComponent.jsx
const FirstComponent = props => {
const {
location: {
state: { userDetails }
}
} = props;
console.log('user-detail', userDetails);
....
some data manipulations
....
}
Here, the FirstComponent is not directly linked or related to the LinkingComponent. I'm using the LinkingComponent in my dashboard and I am passing my 'data' there. It is receiving it properly in the LinkingComponent. But somehow whenever I try to access it in my FirstComponent, it gives me an error that it is undefined. I have already tried various solutions and read various articles, but couldn't find an appropriate solution.
Please help me with the solution here. TIA.
First, get rid of Router or BrowserRouter, because Router is usually just an alias for BrowserRouter. Below is the import you might see:
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
And then, your App.jsx will be like:
<Router>
<Route exact path={'/my-dashboard/first-page'} component={FirstComponent}/>
</Router>
Second, this is seriously wrong:
const FirstComponent = props => {
const {
location: {
state: { userDetails }
}
} = props;
console.log('user-detail', userDetails); // <- Seriously wrong!
return <div>FirstComponent</div>;
}
Although FirstComponent is a function, it's not a normal javascript function but React functional component! You need to have a life circle method (componentDidMount) to see the userDetails prop.
Assuming that you know Hooks, your FirstComponent will be like:
import { useEffect } from "react"
const FirstComponent = props => {
const {
location: {
state: { userDetails }
}
} = props;
useEffect(() => {
console.log({ userDetails }); // <- Better console.log with Object
}, []) // <- empty dependency = componentDidMount
return <div>FirstComponent</div>;
}
Also, you need to wrap your FirstComponent by withRouter HOC, which is provided by react-router, to get the location prop in your FirstComponent's props.
Here I've created a small example. Play with it.
I have already created a HOC in my react app following this, and its working fine. However i was wondering if there is a way to create a HOC as functional component(With or without state)??? since the given example is a class based component.
Tried to find the same over web but couldn't get anything. Not sure if thats even possible?? Or right thing to do ever??
Any leads will be appreciated :)
I agree with siraj, strictly speaking the example in the accepted answer is not a true HOC. The distinguishing feature of a HOC is that it returns a component, whereas the PrivateRoute component in the accepted answer is a component itself. So while it accomplishes what it set out to do just fine, I don't think it is a great example of a HOC.
In the functional component world, the most basic HOC would look like this:
const withNothing = Component => ({ ...props }) => (
<Component {...props} />
);
Calling withNothing returns another component (not an instance, that's the main difference), which can then be used just like a regular component:
const ComponentWithNothing = withNothing(Component);
const instance = <ComponentWithNothing someProp="test" />;
One way to use this is if you want to use ad-hoc (no pun intended lol) context providers.
Let's say my application has multiple points where a user can login. I don't want to copy the login logic (API calls and success/error messages) across all these points, so I'd like a reusable <Login /> component. However, in my case all these points of login differ significantly visually, so a reusable component is not an option. What I need is a reusable <WithLogin /> component, which would provide its children with all the necessary functionality - the API call and success/error messages. Here's one way to do this:
// This context will only hold the `login` method.
// Calling this method will invoke all the required logic.
const LoginContext = React.createContext();
LoginContext.displayName = "Login";
// This "HOC" (not a true HOC yet) should take care of
// all the reusable logic - API calls and messages.
// This will allow me to pass different layouts as children.
const WithLogin = ({ children }) => {
const [popup, setPopup] = useState(null);
const doLogin = useCallback(
(email, password) =>
callLoginAPI(email, password).then(
() => {
setPopup({
message: "Success"
});
},
() => {
setPopup({
error: true,
message: "Failure"
});
}
),
[setPopup]
);
return (
<LoginContext.Provider value={doLogin}>
{children}
{popup ? (
<Modal
error={popup.error}
message={popup.message}
onClose={() => setPopup(null)}
/>
) : null}
</LoginContext.Provider>
);
};
// This is my main component. It is very neat and simple
// because all the technical bits are inside WithLogin.
const MyComponent = () => {
const login = useContext(LoginContext);
const doLogin = useCallback(() => {
login("a#b.c", "password");
}, [login]);
return (
<WithLogin>
<button type="button" onClick={doLogin}>
Login!
</button>
</WithLogin>
);
};
Unfortunately, this does not work because LoginContext.Provider is instantiated inside MyComponent, and so useContext(LoginContext) returns nothing.
HOC to the rescue! What if I added a tiny middleman:
const withLogin = Component => ({ ...props }) => (
<WithLogin>
<Component {...props} />
</WithLogin>
);
And then:
const MyComponent = () => {
const login = useContext(LoginContext);
const doLogin = useCallback(() => {
login("a#b.c", "password");
}, [login]);
return (
<button type="button" onClick={doLogin}>
Login!
</button>
);
};
const MyComponentWithLogin = withLogin(MyComponent);
Bam! MyComponentWithLogin will now work as expected.
This may well not be the best way to approach this particular situation, but I kinda like it.
And yes, it really is just an extra function call, nothing more! According to the official guide:
HOCs are not part of the React API, per se. They are a pattern that emerges from React’s compositional nature.
Definitely you can create a functional stateless component that accepts component as an input and return some other component as an output, for example;
You can create a PrivateRoute component that accepts a Component as a prop value and returns some other Component depending on if user is authenticated or not.
If user is not authenticated(read it from context store) then you redirect user to login page with <Redirect to='/login'/>else you return the component passed as a prop and send other props to that component <Component {...props} />
App.js
const App = () => {
return (
<Switch>
<PrivateRoute exact path='/' component={Home} />
<Route exact path='/about' component={About} />
<Route exact path='/login' component={Login} />
<Route exact path='/register' component={Register} />
</Switch>
);
}
export default App;
PrivateRoute.jsx
import React, { useContext , useEffect} from 'react';
import { Route, Redirect } from 'react-router-dom'
import AuthContext from '../../context/auth/authContext'
const PrivateRoute = ({ component: Component, ...rest }) => {
const authContext = useContext(AuthContext)
const { loadUser, isAuthenticated } = authContext
useEffect(() => {
loadUser()
// eslint-disable-next-line
}, [])
if(isAuthenticated === null){
return <></>
}
return (
<Route {...rest} render={props =>
!isAuthenticated ? (
<Redirect to='/login'/>
) : (
<Component {...props} />
)
}
/>
);
};
export default PrivateRoute;
Higher Order Components does not have to be class components, their purpose is to take a Component as an input and return a component as an output according to some logic.
The following is an over simplified example of using HOC with functional components.
The functional component to be "wrapped":
import React from 'react'
import withClasses from '../withClasses'
const ToBeWrappedByHOC = () => {
return (
<div>
<p>I'm wrapped by a higher order component</p>
</div>
)
}
export default withClasses(ToBeWrappedByHOC, "myClassName");
The Higher Order Component:
import React from 'react'
const withClasses = (WrappedComponent, classes) => {
return (props) => (
<div className={classes}>
<WrappedComponent {...props} />
</div>
);
};
export default withClasses;
The component can be used in a different component like so.
<ToBeWrappedByHOC/>
I might be late to the party but here is my two-cent regarding the HOC
Creating HOC in a true react functional component way is kind of impossible because it is suggested not to call hook inside a nested function.
Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function, before any early returns. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls. (If you’re curious, we’ll explain this in-depth below.)
Rules of Hooks
Here is what I have tried and failed
import React, { useState } from "react";
import "./styles.css";
function Component(props) {
console.log(props);
return (
<div>
<h2> Component Count {props.count}</h2>
<button onClick={props.handleClick}>Click</button>
</div>
);
}
function Component1(props) {
console.log(props);
return (
<div>
<h2> Component1 Count {props.count}</h2>
<button onClick={props.handleClick}>Click</button>
</div>
);
}
function HOC(WrapperFunction) {
return function (props) {
const handleClick = () => {
setCount(count + 1);
};
const [count, setCount] = useState(0);
return (
<WrapperFunction handleClick={handleClick} count={count} {...props} />
);
}
}
const Comp1 = HOC((props) => {
return <Component {...props} />;
});
const Comp2 = HOC((props) => {
return <Component1 {...props} />;
});
export default function App() {
return (
<div className="App">
<Comp1 name="hel" />
<Comp2 />
</div>
);
}
CodeSandBox
Even though the code works in codesandbox but it won't run in your local machine because of the above rule, you should get the following error if you try to run this code
React Hook "useState" cannot be called inside a callback
So to go around this I have done the following
import "./styles.css";
import * as React from "react";
//macbook
function Company(props) {
return (
<>
<h1>Company</h1>
<p>{props.count}</p>
<button onClick={() => props.increment()}>increment</button>
</>
);
}
function Developer(props) {
return (
<>
<h1>Developer</h1>
<p>{props.count}</p>
<button onClick={() => props.increment()}>increment</button>
</>
);
}
//decorator
function HOC(Component) {
// return function () {
// const [data, setData] = React.useState();
// return <Component />;
// };
class Wrapper extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
handleClick = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<Component count={this.state.count} increment={this.handleClick} />
);
}
}
return Wrapper;
}
const NewCompany = HOC(Company);
const NewDeveloper = HOC(Developer);
export default function App() {
return (
<div className="App">
<NewCompany name={"Google"} />
<br />
<NewDeveloper />
</div>
);
}
CodeSandbox
I think for functional component this works fine
import {useEffect, useState} from 'react';
// Target Component
function Clock({ time }) {
return <h1>{time}</h1>
}
// HOC
function app(C) {
return (props) => {
const [time, setTime] = useState(new Date().toUTCString());
useEffect(() => {
setTimeout(() => setTime(new Date().toUTCString()), 1000);
})
return <C {...props} time={time}/>
}
}
export default app(Clock);
You can test it here: https://codesandbox.io/s/hoc-s6kmnv
Yes it is possible
import React, { useState } from 'react';
const WrapperCounter = OldComponent =>{
function WrapperCounter(props){
const[count,SetCount] = useState(0)
const incrementCounter = ()=>{
SetCount(count+1)
}
return(<OldComponent {...props} count={count} incrementCounter={incrementCounter}></OldComponent>)
}
return WrapperCounter
}
export default WrapperCounter
import React from 'react';
import WrapperCounter from './WrapperCounter';
function CounterFn({count,incrementCounter}){
return(
<button onClick={incrementCounter}>Counter inside functiona component {count}</button>
)
}
export default WrapperCounter(CounterFn)
I am following this tutorial: https://crypt.codemancers.com/posts/2017-06-03-reactjs-server-side-rendering-with-router-v4-and-redux/ which i think is the 'standard' way of doing server side rendering in react (?).
Basically what happens is i use react router (v4) to make a tree of all the components that are about to get rendered:
const promises = branch.map(({ route }) => {
return route.component.fetchInitialData
? route.component.fetchInitialData(store.dispatch)
: Promise.resolve();
});
Wait for all those promises to resolve and then call renderToString.
In my components i have a static function called fetchInitialData which looks like this:
class Users extends React.Component {
static fetchInitialData(dispatch) {
return dispatch(getUsers());
}
componentDidMount() {
this.props.getUsers();
}
render() {
...
}
}
export default connect((state) => {
return { users: state.users };
}, (dispatch) => {
return bindActionCreators({ getUsers }, dispatch);
})(Users);
And all this works great except that getUsers is called both on the server and the client.
I could of course check if any users are loaded and not call getUsers in componentDidMount but there must be a better, explicit way to not make the async call twice.
After getting more and more familiar with react i feel fairly confident i have a solution.
I pass a browserContext object along all rendered routes, much like staticContext on the server. In the browserContext i set two values; isFirstRender and usingDevServer. isFirstRender is only true while the app is rendered for the first time and usingDevServer is only true when using the webpack-dev-server.
const store = createStore(reducers, initialReduxState, middleware);
The entry file for the browser side:
const browserContext = {
isFirstRender: true,
usingDevServer: !!process.env.USING_DEV_SERVER
};
const BrowserApp = () => {
return (
<Provider store={store}>
<BrowserRouter>
{renderRoutes(routes, { store, browserContext })}
</BrowserRouter>
</Provider>
);
};
hydrate(
<BrowserApp />,
document.getElementById('root')
);
browserContext.isFirstRender = false;
USING_DEV_SERVER is defined in the webpack config file using webpack.DefinePlugin
Then i wrote a HOC component that uses this information to fetch initial data only in situations where it is needed:
function wrapInitialDataComponent(Component) {
class InitialDatacomponent extends React.Component {
componentDidMount() {
const { store, browserContext, match } = this.props;
const fetchRequired = browserContext.usingDevServer || !browserContext.isFirstRender;
if (fetchRequired && Component.fetchInitialData) {
Component.fetchInitialData(store.dispatch, match);
}
}
render() {
return <Component {...this.props} />;
}
}
// Copy any static methods.
hoistNonReactStatics(InitialDatacomponent, Component);
// Set display name for debugging.
InitialDatacomponent.displayName = `InitialDatacomponent(${getDisplayName(Component)})`;
return InitialDatacomponent;
}
And then the last thing to do is wrap any components rendered with react router with this HOC component. I did this by simply iterating over the routes recursively:
function wrapRoutes(routes) {
routes.forEach((route) => {
route.component = wrapInitialDataComponent(route.component);
if (route.routes) {
wrapRoutes(route.routes);
}
});
}
const routes = [ ... ];
wrapRoutes(routes);
And that seems to do the trick :)