I have a redux reducer loaded with several reactjs components.
I want to load these inside other components through this.props
Like: this.props.components.MyReactComponent
class OtherComponent extends Component {
render() {
const Component = this.props.components.MyReactComponent
return (
<div>
<Component />
</div>
)
}
}
Is this possible? If so, how?
EDIT The component is a connected component. I am able to load it but it is broken. In this case, it is a counter, when you click to increment or decrement nothing happens. In the console, there is this error:
Uncaught ReferenceError: _classCallCheck is not defined
if I convert the component into a dumb component (without connecting it), the error is this:
Uncaught ReferenceError: _classCallCheck3 is not defined
EDIT 2
I found out why those errors show up. It is because the react component gets stripped out when stored in the reducer:
A react component would look something like this:
{ function:
{ [Function: Connect]
displayName: 'Connect(Counter)',
WrappedComponent: { [Function: Counter] propTypes: [Object] },
contextTypes: { store: [Object] },
propTypes: { store: [Object] } } }
However, after I store it inside a reducer, it loses its properties and ends up looking something like this:
{ function:
{ [Function: Connect] } }
After reading the comments below, I thought of an alternative. I can store in a reducer the path to each component, then make a new wrapper component that could render those other components from those paths.
I tried it but encoutered a different problem with the funcion require from nodejs that for some weird reason is not letting me user a variable as an argument. For example:
This works:
var SomeContent = require('../extensions/myContent/containers')
This does not:
var testpath = '../extensions/myContent/containers'
var SomeContent = require(testpath)
Giving me the following error:
Uncaught Error: Cannot find module '../extensions/myContent/containers'.
It is adding a period at the end of the path. How can I prevent require to add that period?
If you can think of any other alternative I can implement for what I am trying to do, I would greatly appreciate it.
EDIT 3 Following Thomas advice...
What I am trying to accomplish is this:
I want to be able to render react components inside other react components, I know how to do it the same way most us know how to; however, I want to be able to do it by importing a file that would contain all the components without actually having to import and export each one of them:
OtherComponent.js
import React, { Component } from 'react'
import { SomeComponent } from '../allComponentes/index.js'
export default class OtherComponent extends Component {
render() {
return (
<SomeComponent />
)
}
}
SomeComponent.js
import React, { Component } from 'react'
export default class SomeComponent extends Component {
render() {
return (
<div>
Hello
</div>
)
}
}
allComponents/index.js
import SomeComponent from '../allComponents/SomeComponent/index.js'
export { SomeComponent }
What I am trying to do in allComponents/index.js is to avoid having import/export statements for each component by reading (with fs module) all the components inside the allComponents folder and export them.
allComponents/index.js (pseudocode)
get all folders inside allComponents folder
loop through each folder and require the components
store each component inside an object
export object
When I tried that, I encountered multiple issues, for one, export statements have to be in the top-level, and second, fs would work only on the server side.
So, that is why I thought of loading all the components in a reducer and then pass them as props. But as I found out, they got stripped out when stored them in a reducer.
Then, I thought of only storing the path to those components inside a reducer and have a wrapper component that would use that path to require the needed component. This method almost worked out but the nodejs function require wont allow me to pass a variable as an argument (as shown in EDIT 2)
I think your question is not really to do with redux but rather is (as you say):
What I am trying to do in allComponents/index.js is to avoid having import/export statements for each component by reading (with fs module) all the components inside the allComponents folder and export them.
By way of example, I have all of my (dumb) form components in a folder path components/form-components and the index.js looks something like:
export FieldSet from './FieldSet'
export Input from './Input'
export Label from './Label'
export Submit from './Submit'
export Select from './Select'
export Textarea from './Textarea'
Then when I want to import a component elsewhere, it is import { FieldSet, Label, Input, Submit } from '../../components/form-components/';
Related
ReactJS is a great library, However, it misses some features which I found in Vue and Angular. These features can be implemented of course in React, however, they require extra code to be written.
Every react component, or every JSX element I should say has the following properties shared, which are given by React to us to consume:
ref
key
I wanted to add extra props:
renderIf
fallback
These props help in a way I can't describe when it comes to conditional rendering and filtering the views based on the logged-in user permissions and roles (and other conditional rendering use cases, of course).
In react, if we wanted to apply these props to our components, we would use a HOC as follows:
// 🍎 Disclaimer: you don't have to understand any of the code written bellow, the general idea is that this is a HOC.
import React from 'react'
import getVal from './getVal'
export default function EnhancedComponent(OriginalComponent) {
return ({ renderIf: renderIf_ = true, override: override_, fallback: fallback_ = undefined, ...props }) => {
const renderIf = getVal(renderIf_)
const override = getVal(override_)
const fallback = getVal(fallback_)
const consumersComponent = <OriginalComponent {...props} />
let render = fallback
if (renderIf) render = consumersComponent
if (override_ !== undefined) render = override
return render
}
}
Where every time you want to apply these props to your components, you would have to wrap every new component you create with EnhancedComponent as follows:
export default EnhancedComponent(function Sidenav(){
return <div> side nav </div>
})
Now, you can use your Sidenav component within your App component as follows:
import Sidenav from './Sidenav'
export default function App(){
return (
<div>
<Sidenav renderIf={(5 + 5 === 10)}/>
<div>etc</div>
</div>
)
}
This API is great, but it has a drawback, which is, every time you want to apply these cool props (renderIf and fallback) you'll have to repeat these steps:
import Enhanced component to your file.
wrap your export with Enhanced component.
What I am looking for, is a method, or a way to inherit, or to add some props to the original react component class, somehow?
In react class components, I can imagine doing this on the React.Component class which we used to extend from in the past
class Car extends React.Component{
constructor(){}
render(){
return <div>I miss you 🌹</div>
}
}
But in react functional component, how can we do that?
I want to apply these props by default everytime I create a new component, without wrapping my components in a HOC everytime.
Does React have a way to do that? To change its defaults ?
For example I could have a component
class HomeDefault extends PureComponent {
...
}
and I could add a property after the instance like so before the export
HomeDefault.someProperty = {test: 'test'}
Now if I were to use something like a higher order component over my HomeDefault component, I could console log out this "someProperty" object that I just set.
export default withHOC(HomeDefault)
// withHOC.js
import React from 'react';
const withHOC = Component => {
const WithHOC = props => {
console.log("the property: ", Component.someProperty) // this will work with the example above
return <Component />;
};
return WithHOC;
};
export default withNavigation;
So THAT works, but I need a solution that can set this property from within a useEffect (for functional components) or a componentDidMount (for class)
Is this possible??
The actual use case is that I'm upgrading react navigation and the .navigationOptions property has been done away with but I'm trying to not completely redo our code for the time being. So it would be awesome t run a function in a useEffect/componentDidMount and set this property that way since navigationOptions doesn't make the navigation state available outside of the component.
I hope this all makes sense! thank you all
I am rendering my react component inside an existing JSP page using
ReactDOM.render(
React.createElement(MyReactComponents.myReactComponent, {
props
}),
document.querySelector("#id")
);
and the react component is as follows:
import MyStore from "./MyStore";
const MyReactComponent: React.FC<any> = (props: any) => {
const store = useContext(MyStore);
store.myFunction();
---code---
}
and MyStore is as follows:
export class MyStore{
---Code---
}
export default createContext(new MyStore());
But i'm getting this error:
And one more importing thing to notice is that when I'm trying to render this react component on top of another existing react component, i'm not getting any error and everything is working fine.
Can someone please explain me what might be causing the issue?
I'm not sure, but maybe you are misusing the useContext hook?
Whenever you use it inside a component Child, then at least one of its parent component must call the <Context>.Provider, so that it is initialized down the tree.
In your example, you render MyReactComponent using ReactDOM.render: due this, I suppose MyReactComponent is the first component in your tree. If that is the case, when you use useContext inside it, it cannot find any MyStore context.
So, probably, you just need to wrap your MyReactComponent with a context provider.
export class MyStore { ... }
export const MyStoreContext = createContext(new MyStore());
---
ReactDOM.render(
<MyStoreContext.Provider>
<MyReactComponent {...props />
</MyStoreContext.Provider>
, document.querySelector("#id"));
And then, inside MyReactComponent, you can use const store = useContext(MyStoreContext);.
[Create-React-App] Jest and Enzyme(3.9.0) cant seem to find my <Button/> Element from Auth.jx container..
The application should render the Auth container if(!isAuthernticated) and the button should be disabled if no input is supplied.
I tried ShallowWrapper::dive() but i get a TypeError
TypeError: SHallowWrapper::dive() can only be called on components
Auth.jsx
//...
let errorMessage = null;
let button=<Button id='Disabled' btnType="Success" disabled={false}>Disabled</Button>;
let authRedirect = null;
if (this.props.isAuthenticated) {
authRedirect = <Redirect to={this.props.authRedirectPath}/>
}
if (this.state.controls.username.value && this.state.controls.password.value){
button=<Button id='Login' btnType="Success">Login</Button>
}
return (
<div>
{authRedirect}
<form onSubmit={this.handleSubmit}>
{form}
{button}
</form>
</div>
)
}
//...
Auth.test.js
import React from 'react';
import {shallow} from 'enzyme';
import Auth from '../containers/Auth/Auth';
import Button from '../components/Button/button';
import Input from '../components/Input/input';
describe('<Auth/>',() =>{
let wrapper;
beforeEach(()=>{
wrapper=shallow(<Auth authRedirectPath='/' isAuthenticated={false}/>).dive()
})
//Test 1
it('should render disabled button if no input has been specified ',()=>{
expect(wrapper.find(Button).text()).toEqual('Disabled')
});
})
I don't believe you should be calling dive() on the wrapper in your test. Instead, you should shallow render your wrapper and then call dive() or render() on the found Button to test for its text.
So, first:
wrapper = shallow(<Auth authRedirectPath='/' isAuthenticated={false} />)
Then, when you want to find(Button) and test for its text when rendered, you would do either of the following:
expect(wrapper.find(Button).dive().text()).toEqual('Disabled')
// OR
expect(wrapper.find(Button).render().text()).toEqual('Disabled')
To demonstrate this, I've re-created a skeleton of your code here at this code sandbox. You can see, specifically in Auth.test.js how I have modified your original test with my code lines above. If you click on "Tests" in the bottom toolbar, you'll see that the test passes.
If you go into Auth.jsx and you change the username and password values - thereby affecting the Button text - then the test will fail.
My comment above has explored that you use Redux's connect HOC on the component. That's why you can't access the desired component since that's a level deeper within the tree.
I'd suggest reading my article on Medium in which you can find some details about the actual problem and also the appropriate solution.
EDIT
If you're still experiencing the same issue, I'd suggest the following:
Let's suppose that your Auth component is something like this.
import React, { Component } from 'react';
import { connect } from 'react-redux';
export class Auth extends Component {
// Something happens here.
}
export default connect(mapStateToProps, mapDispatchToprops)(Auth);
Notice that I used the export keyword in both cases. That being said, you can test the proper component without any connection to Redux and it also reduces the generated tree.
Pay attention to import the named export class within the test file:
...
import { Auth } from './Auth';
...
I'm using React and Redux. I have a component which loads ChildComponent and depending on Redux's state will also load MainComponent
const ChooseIndex = ({ appInitMount }) => {
return (
<>
<ChildComponent />
{!appInitMount && <MainComponent />}
</>
);
};
const mapStateToProps = ({ main }) => {
return {
appInitMount: main.appInitMount
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(ChooseIndex);
I'm trying to write a test to check that ChildComponent is loaded:
import React from "react";
import { render } from "react-testing-library";
import ChooseIndex from "../choose-index";
test("ChooseIndex should call ChildComponent", () => {
const wrapper = render(
<ChooseIndex />
);
});
I get this error:
Error: Uncaught [Invariant Violation: Could not find "store" in either
the context or props of "Connect(ChooseIndex)". Either wrap the root
component in a , or explicitly pass "store" as a prop to
"Connect(ChooseIndex)".]
Should I mock Redux by passing an object literal to ChooseIndex? Or should I create a Redux store (as my real application does) for every test?
Try to render your component like this:
render(
<Provider store={store}>
<ChooseIndex />
</Provider>
)
And pass the actual store you use in your app. In this way, you're testing the real logic that you'll use in production. You also don't care what actions get dispatched and what's in the state. You look at what gets rendered and interact with the UI—which is what matters in the end.
Separating the component from Redux and testing the two in isolation is against the whole point of react-testing-library. You want to test your app as a real user would.
If you check out the writing tests section of the redux docs, there is an example of testing a connected component.
when you import it [A redux connected component], you're actually holding the wrapper component returned by connect(), and not the App component itself. If you want to test its interaction with Redux, this is good news: you can wrap it in a with a store created specifically for this unit test. But sometimes you want to test just the rendering of the component, without a Redux store.
In order to be able to test the App component itself without having to deal with the decorator, we recommend you to also export the undecorated component
As with most unit tests, you really want to be testing your components, and not that redux is working correctly. So the solution for you is to export both the component and the connected component, while only testing the component itself, and providing whatever props redux is passing to your component.
import { connect } from 'react-redux'
// Use named export for unconnected component (for tests)
export class App extends Component {
/* ... */
}
// Use default export for the connected component (for app)
export default connect(mapStateToProps)(App)