How to inject mobx store into a stateless component - javascript

I am using mobx and react in a web application and I want to find a way to pass mobx store state to a stateless component. Below is my current component source code:
import React from 'react';
import Panel from './Panel';
import {inject, observer} from 'mobx-react';
#inject(allStores => ({
form: allStores.store.form,
}))
#observer
export default class Creator extends React.Component {
connect() {
console.log(this.props.form);
};
render() {
return (
<Panel form={this.props.form} connect={this.connect.bind(this)}/>
);
}
};
How can I change it to be stateless? I tried below code but didn't work:
const Creator = ({form}) => {
const connect = ()=>{
console.log('xxxx,', form);
}
return (
<Panel form={form} connect={connect}/>
);
}
export default observer(Creator);
when I run above code, I got undefined value for form on the connect method. How can I inject the store into stateless component? I tried to use #inject on top of stateless component, but got a syntax error.

inject returns a function that you can use on a observer functional component:
var Example = inject("myStore")(observer((props) => {
// ...
}));

A variant of #Tholle answer :
const Example = inject("myStore")(observer(({myStore, otherProp}) => {
// ...
}));

const Example = inject(
'YOUR_STORE1',
'YOUR_STORE2'
)(
observer(({ YOUR_STORE1, YOUR_STORE2, OTHER_PROPS }) => {
return (
// Your Design
)
})
)
export default Example

Related

Unable to mock the component with the given set of mocked data using React Testing Library

This is my component and I am trying to mock method fetchChapterData which would return me the mocked data and would set the state of the component with the returned value but it is not rendering the UI with the mocked data and also re-rendering the component multiple times.
import React, { Component } from 'react'
import LoadingImg from '../../assets/images/loader.gif'
import {fetchChapterData} from '../helpers/common'
class MyComponent extends Component {
constructor(props){
super(props)
this.state={
isDataLoaded:false
}
}
render() {
return (
<div className="abc">
{this.state.isDataLoaded?
<div className="abc">
<div className="abc" dangerouslySetInnerHTML={{__html: this.state.data}}></div>
</div>
:
<div className="loading" key="loading_key">
<img src={LoadingImg} alt ="Loading"/>
</div>
}
</div>
)
}
componentDidMount= async()=> {
let result = await fetchChapterData('IntroductionReportChapter')
/*fetchChapterData is being mocked with data {isDataLoaded:true,data:"<p>hello all</p>"}*/
this.setState({...result});
}
}
export default MyComponent;
This is my Test file
so here I am mocking the method fetchChapterData and asking it to return the fake data
import React from 'react'
import MyComponent from './myComponent'
import { render,screen} from "#testing-library/react"
import {fetchChapterData} from '../helpers/common'
jest.mock('../helpers/common', () => ({
fetchChapterData:jest.fn(),
}));
let container = document.createElement("div");
document.body.appendChild(container);
const fakeData={
"isDataLoaded": true,
"data":"<p>hello</p>"
};
describe("Introduction component testing", () => {
it('Introduction section rendered or not',async()=>{
await fetchChapterData.mockResolvedValueOnce({...fakeData})
render(<MyComponent />)
await expect(fetchChapterData).toHaveBeenCalledTimes(1);
})
});
So I got the solution for the same. We need to use the asynchronous version of act to apply resolved promises while rendering the component in the test file, and it would start executing your mocked data.
describe("My component testing", () => {
it("renders user data", async () => {
const fakeData={isDataLoaded:true,intoductionText:" <p>manisha</p>"}
fetchChapterData.mockResolvedValueOnce({...fakeData})
await act(async () => {
render(<MyComponent />, container);
});
screen.getByText('manisha')
});
})
Hey – try mocking your data helper like so
jest.mock('../helpers/common', () => ({
fetchChapterData:jest.fn(() => Promise.resolve({
//Your data here
}))
}))
Also in your state object at the top of the class, you have false1 instead of false do you want to correct that?
You may also want to take another approach to updating the data loaded state property, maybe instead create a helper function in the class that sets that state like so
const handleData = (data) => {
setState({
isDataLoaded: true,
data
})
}
Finally, call this helper in componentDidMount with the result from your fetchChapterData function

Use imported function in return method (invalid hook call, react js)

Wassup Guys,
I have a reusable component, that translates keys into a choosen language through keynames as string or a binded var.
Usually I use a tag for this, but because of different reasons I am switching/replacing current translations with {t('...')}.
Here is the code of the component:
import React from 'react';
import { useTranslation as defaultTranslation } from 'react-i18next';
import i18next from 'i18next';
export const useTranslation = (ns = 'common', options) => {
return defaultTranslation(ns, {
useSuspense: false,
...options,
});
};
export const withTranslation = (Component, ns, options) => {
const TranslatedHOC = (props) => {
const translationProps = useTranslation(ns, options);
return <Component {...translationProps} {...props} />;
};
return TranslatedHOC;
};
export const getCurrentLanguage = () =>
i18next.language || localStorage.getItem('language') || 'de-DE';
First of all I define the const for the used imported function:
const {t} = useTranslation();
normal case: importing my component in the file, where I want to use it and add code above.
Code of my component, where I want to replace the Tags.
// Import React Table
import ReactTable from 'react-table';
import 'react-table/react-table.css';
import LocalizedText from '#components/i18n/LocalizedText';
class T extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
pages: null,
loading: true,
};
this.fetchData = this.fetchData.bind(this);
}
fetchData(state, instance) {
this.props.onFetchData(state, instance).then(() => {
this.setState({
loading: false,
});
});
}
render() {
return (
<ReactTable
{...this.props}
previousText={
<LocalizedText textKey="caseoverview.orderproduct.back" />
}
nextText={
<LocalizedText textKey="caseoverview.orderproduct.continue" />
}
loadingText={<LocalizedText textKey="loading" />}
noDataText={<LocalizedText textKey="placeholders.nodata" />}
pageText={
<LocalizedText textKey="reservationcalculator.search.result.page" />
}
ofText={<LocalizedText textKey="from" />}
rowsText={<LocalizedText textKey="rows" />}
className="case-search-table"
/>
);
}
}
export default T;
...
previousText={
<LocalizedText textKey="caseoverview.orderproduct.back" />
}
...
should change to:
...
previousText={
t('caseoverview.orderproduct.back')
}
...
The problem is, that I can't use the code quoted above without getting any issues regarding invalid hook calls. If I move it out somehow, I get errors telling me that my 't' is either not defined or an unexpected token. Could someone help me out? Searched online for solutios without any result.
A hook can only be used in a functional component. You can change this class component to a functional component, or you can use react-i18next's withTranslation HOC to wrap your class component. See https://react.i18next.com/latest/withtranslation-hoc#when-to-use
#kevin asworth answer helped me out.
Using withTranslation with passing t as prop
const {t} = this.props;
inside the render method worked for me.

React: Array.reduce + Object.assign for dynamic hookrouter routes complains re: PascalCase [duplicate]

I am trying to dynamically render components based on their type.
For example:
var type = "Example";
var ComponentName = type + "Component";
return <ComponentName />;
// Returns <examplecomponent /> instead of <ExampleComponent />
I tried the solution proposed here React/JSX dynamic component names
That gave me an error when compiling (using browserify for gulp). It expected XML where I was using an array syntax.
I could solve this by creating a method for every component:
newExampleComponent() {
return <ExampleComponent />;
}
newComponent(type) {
return this["new" + type + "Component"]();
}
But that would mean a new method for every component I create. There must be a more elegant solution to this problem.
I am very open to suggestions.
EDIT:
As pointed out by gmfvpereira these days there is an official documentation entry for this:
https://reactjs.org/docs/jsx-in-depth.html#choosing-the-type-at-runtime
<MyComponent /> compiles to React.createElement(MyComponent, {}), which expects a string (HTML tag) or a function (ReactClass) as first parameter.
You could just store your component class in a variable with a name that starts with an uppercase letter. See HTML tags vs React Components.
var MyComponent = Components[type + "Component"];
return <MyComponent />;
compiles to
var MyComponent = Components[type + "Component"];
return React.createElement(MyComponent, {});
There is an official documentation about how to handle such situations is available here: https://facebook.github.io/react/docs/jsx-in-depth.html#choosing-the-type-at-runtime
Basically it says:
Wrong:
import React from 'react';
import { PhotoStory, VideoStory } from './stories';
const components = {
photo: PhotoStory,
video: VideoStory
};
function Story(props) {
// Wrong! JSX type can't be an expression.
return <components[props.storyType] story={props.story} />;
}
Correct:
import React from 'react';
import { PhotoStory, VideoStory } from './stories';
const components = {
photo: PhotoStory,
video: VideoStory
};
function Story(props) {
// Correct! JSX type can be a capitalized variable.
const SpecificStory = components[props.storyType];
return <SpecificStory story={props.story} />;
}
There should be a container that maps component names to all components that are supposed to be used dynamically. Component classes should be registered in a container because in modular environment there's otherwise no single place where they could be accessed. Component classes cannot be identified by their names without specifying them explicitly because function name is minified in production.
Component map
It can be plain object:
class Foo extends React.Component { ... }
...
const componentsMap = { Foo, Bar };
...
const componentName = 'Fo' + 'o';
const DynamicComponent = componentsMap[componentName];
<DynamicComponent/>;
Or Map instance:
const componentsMap = new Map([[Foo, Foo], [Bar, Bar]]);
...
const DynamicComponent = componentsMap.get(componentName);
Plain object is more suitable because it benefits from property shorthand.
Barrel module
A barrel module with named exports can act as such map:
// Foo.js
export class Foo extends React.Component { ... }
// dynamic-components.js
export * from './Foo';
export * from './Bar';
// some module that uses dynamic component
import * as componentsMap from './dynamic-components';
const componentName = 'Fo' + 'o';
const DynamicComponent = componentsMap[componentName];
<DynamicComponent/>;
This works well with one class per module code style.
Decorator
Decorators can be used with class components for syntactic sugar, this still requires to specify class names explicitly and register them in a map:
const componentsMap = {};
function dynamic(Component) {
if (!Component.displayName)
throw new Error('no name');
componentsMap[Component.displayName] = Component;
return Component;
}
...
#dynamic
class Foo extends React.Component {
static displayName = 'Foo'
...
}
A decorator can be used as higher-order component with functional components:
const Bar = props => ...;
Bar.displayName = 'Bar';
export default dynamic(Bar);
The use of non-standard displayName instead of random property also benefits debugging.
With the introduction of React.lazy, we can now use a true dynamic approach to import the component and render it.
import React, { lazy, Suspense } from 'react';
const App = ({ componentName, ...props }) => {
const DynamicComponent = lazy(() => import(`./${componentName}`));
return (
<Suspense fallback={<div>Loading...</div>}>
<DynamicComponent {...props} />
</Suspense>
);
};
This approach makes some assumptions about the file hierarchy of course and can make the code easy to break.
I figured out a new solution. Do note that I am using ES6 modules so I am requiring the class. You could also define a new React class instead.
var components = {
example: React.createFactory( require('./ExampleComponent') )
};
var type = "example";
newComponent() {
return components[type]({ attribute: "value" });
}
For a wrapper component, a simple solution would be to just use React.createElement directly (using ES6).
import RaisedButton from 'mui/RaisedButton'
import FlatButton from 'mui/FlatButton'
import IconButton from 'mui/IconButton'
class Button extends React.Component {
render() {
const { type, ...props } = this.props
let button = null
switch (type) {
case 'flat': button = FlatButton
break
case 'icon': button = IconButton
break
default: button = RaisedButton
break
}
return (
React.createElement(button, { ...props, disableTouchRipple: true, disableFocusRipple: true })
)
}
}
Across all options with component maps I haven't found the simplest way to define the map using ES6 short syntax:
import React from 'react'
import { PhotoStory, VideoStory } from './stories'
const components = {
PhotoStory,
VideoStory,
}
function Story(props) {
//given that props.story contains 'PhotoStory' or 'VideoStory'
const SpecificStory = components[props.story]
return <SpecificStory/>
}
If your components are global you can simply do:
var nameOfComponent = "SomeComponent";
React.createElement(window[nameOfComponent], {});
Having a map doesn't look good at all with a large amount of components. I'm actually surprised that no one has suggested something like this:
var componentName = "StringThatContainsComponentName";
const importedComponentModule = require("path/to/component/" + componentName).default;
return React.createElement(importedComponentModule);
This one has really helped me when I needed to render a pretty large amount of components loaded in a form of json array.
Assume we have a flag, no different from the state or props:
import ComponentOne from './ComponentOne';
import ComponentTwo from './ComponentTwo';
~~~
const Compo = flag ? ComponentOne : ComponentTwo;
~~~
<Compo someProp={someValue} />
With flag Compo fill with one of ComponentOne or ComponentTwo and then the Compo can act like a React Component.
Assuming you are able to export * from components like so...
// src/components/index.js
export * from './Home'
export * from './Settings'
export * from './SiteList'
You can then re-import * into a new comps object, which can then be used to access your modules.
// src/components/DynamicLoader.js
import React from 'react'
import * as comps from 'components'
export default function ({component, defaultProps}) {
const DynamicComponent = comps[component]
return <DynamicComponent {...defaultProps} />
}
Just pass in a string value that identifies which component you want to paint, wherever you need to paint it.
<DynamicLoader component='Home' defaultProps={someProps} />
Suspose we wish to access various views with dynamic component loading.The following code gives a working example of how to accomplish this by using a string parsed from the search string of a url.
Lets assume we want to access a page 'snozberrys' with two unique views using these url paths:
'http://localhost:3000/snozberrys?aComponent'
and
'http://localhost:3000/snozberrys?bComponent'
we define our view's controller like this:
import React, { Component } from 'react';
import ReactDOM from 'react-dom'
import {
BrowserRouter as Router,
Route
} from 'react-router-dom'
import AComponent from './AComponent.js';
import CoBComponent sole from './BComponent.js';
const views = {
aComponent: <AComponent />,
console: <BComponent />
}
const View = (props) => {
let name = props.location.search.substr(1);
let view = views[name];
if(view == null) throw "View '" + name + "' is undefined";
return view;
}
class ViewManager extends Component {
render() {
return (
<Router>
<div>
<Route path='/' component={View}/>
</div>
</Router>
);
}
}
export default ViewManager
ReactDOM.render(<ViewManager />, document.getElementById('root'));
πŸ‘ You can create a reusable component with a fallback component.
export const StringComponent = (Base, { name, Fallback = undefined, ...rest }) => {
const Component = Base[name];
// return fallback if the component doesn't exist
if (!Component) return <Fallback/>
return <Component {...rest}/>;
};
And call it like this:
import * as Pages from "../pages"
const routes = [
{path: "/", element: "Home" },
{path: "/about", element: "About" },
{path: "*", element: "NotFound" },
]
export function App(){
const Fallback = Pages.NotFound
// render each route using a string as name
return (
<div>
{
routes.map(page =>
StringComponent(Pages, { name: page.element, Fallback })
)
}
</div>
)
}
OBS: Imported Pages needs to be something like this:
import Home from "./home"
import About from "./about"
import NotFound from "./not-found"
export { Home, About, NotFound }
I used a bit different Approach, as we always know our actual components so i thought to apply switch case.
Also total no of component were around 7-8 in my case.
getSubComponent(name) {
let customProps = {
"prop1" :"",
"prop2":"",
"prop3":"",
"prop4":""
}
switch (name) {
case "Component1": return <Component1 {...this.props} {...customProps} />
case "Component2": return <Component2 {...this.props} {...customProps} />
case "component3": return <component3 {...this.props} {...customProps} />
}
}
Edit: Other answers are better, see comments.
I solved the same problem this way:
...
render : function () {
var componentToRender = 'component1Name';
var componentLookup = {
component1Name : (<Component1 />),
component2Name : (<Component2 />),
...
};
return (<div>
{componentLookup[componentToRender]}
</div>);
}
...

How to Programmatically Provide and Consume Context?

So my question is a simple one. In React js I want to pass some states and handlers from a parent to its 3rd grandchild using Context. I have implemented this within the jsx but I want to use the states within the javascript o that I have some logic before I completely output my states.
I have divided my question into 2 parts. 1.) What I have done so far. 2.) What I want to do essentially.
1.)
// this file just stores the Context
MyContext.js
import React, { Component } from 'react';
export const MyContext = React.createContext();
MyProvider.js // this class is used by the parent and the child to have access to the provider
import React, { Component } from 'react';
import {MyContext} from '../MyContext'
class MyProvider extends Component {
state = {
name: 'Wes',
age: 100,
cool: true
}
render() {
return (
<MyContext.Provider value={{
state: this.state,
growAYearOlder: () => this.setState({
age: this.state.age + 1
})
}}>
{this.props.children}
</MyContext.Provider>
)
}
}
export default MyProvider;
// Ok so now I am basically skipping the parent and showing you the consumer grand-child
Person.js
import React, { Component } from 'react';
// first we will make a new context
import { MyContext } from '../MyContext';
class Person extends Component {
render() {
return (
<div className="person">
<MyContext.Consumer>
{(context) => (
<React.Fragment>
<p>Age: {context.state.age}</p>
<p>Name: {context.state.name}</p>
<button onClick={context.growAYearOlder}>🍰πŸ₯πŸŽ‚</button>
</React.Fragment>
)}
</MyContext.Consumer>
</div>
)
}
}
export default Person;
2.)
// Ok so as you can see here I have had to immediately use the context.growAYearOlder. What I want to do instead is have control of it using javascript and modify it as desired; So something like this:
Child.js
const parentContext = MyContext.getContext();
if(somethingHappens){
parentContext().growAYearOlder();
}
return(
// The now rendered component
);
I tried something like this but it doesnt work:
MyContext.Consumer.context.growAYearOlder();
There are many similar questions with proper answers, docs, examples and so on - but this question kept popping up for me.
So, in case you want to get the context value and use it within your component's render() just import it (export context itself not only provider) and use _currentValue e.g.
const contextData = MyContext._currentValue;
Note that you still have to wrap your components with your given context provider.
Also note that for function components, you need to use useContext e.g.
const contextData = useContext(MyContext);
And for class components you can assign the context to a static var and then use it e.g.
class Main extends React.Component(){
static contextType = MyContext;
componentDidMount(){
const contextData = this.context;
}
render() {
return (
<p>Hey</p>
);
}
Note that the static var has to be called contextType otherwise this.context won't hold the MyContext data.
I've based my answer solely from the docs itself(https://reactjs.org/docs/context.html#updating-context-from-a-nested-component)
import React, { Component } from 'react';
import { MyContext } from '../MyContext'
class MyProvider extends Component {
constructor(props) {
super(props)
// I've moved the state declaration inside the constructor
this.state = {
name: 'Wes',
age: 100,
cool: true
}
// moved the function here and added prevState
this.growAYearOlder = () => {
this.setState(prevState => ({
age: prevState.age + 1,
}))
};
}
render() {
return (
<MyContext.Provider value={{
state: this.state,
growAYearOlder: this.growAYearOlder,
}}>
{this.props.children}
</MyContext.Provider>
)
}
}
export default MyProvider;

How to make a generic 'filter' Higher-Order Component in React.js?

I am making a Higher-Order Component in my React.js (+ Redux) app, to abstract the functionality to filter a list of elements with the string received from an input element.
My filtering HOC is,
filter.js
import React, { Component } from 'react'
export default function Filter(FilteredComponent) {
return class FilterComponent extends Component {
constructor(props) {
super(props)
}
generateList() {
if (this.props.searchTerm !== undefined) {
let re = new RegExp(state.searchTerm,'gi')
return this.props.currencyList.filter((c) => c.match(re))
}
else {
return this.props.currencyList
}
}
render() {
return (
<FilteredComponent
filteredList={this.generateList()}
{...this.props}
/>
)
}
}
}
Right now, I am unable to access the filteredList as props.filteredList in the SearchResults component.
The component to display the list is
SearchResults.js
import React from 'react'
const SearchResults = (props) => {
const listData = props.filteredList.map (item => <div>{item}</div>)
return (
<div>
Here are the search results.
<br />
<input
type="text"
value={props.searchTerm}
onChange={props.setSearchTerm}
/>
{listData}
</div> ) }
export default SearchResults
How do I go on about this?
EDIT:
Adding the container component for greater clarity:
SearchContainer.js
import {connect} from 'react-redux'
import SearchResults from '../components/SearchResults'
import * as a from '../actions'
import Filter from '../enhancers/filter'
const getSearchTerm = (state) => (state.searchTerm === undefined) ? '' : state.searchTerm
const mapStateToProps = (state) => {
return {
searchTerm: getSearchTerm(state),
currencyList: state.currencyList
}
}
const mapDispatchToProps = (dispatch) => {
return {
setSearchTerm: (e) => {
dispatch(a.setSearchTerm(e.target.value))
}
}
}
const SearchResultsContainer = connect(
mapStateToProps,
mapDispatchToProps
)(SearchResults)
export default Filter(SearchResultsContainer)
Let’s first think of components as a function that takes a props and returns a Virtual DOM.
Thus the SearchResult component takes these props:
filteredList
searchTerm
setSearchTerm
The higher-order-component created created by connect() provides these props:
searchTerm
currencyList
The Filter() higher-order component:
takes currencyList
provides filteredList
Therefore, you have to wire it like this so that each part receives the props it needs:
connect(...) β†’ Filter β†’ SearchResult
It should look like this:
export default connect(...)(Filter(SearchResult))
Or if you use recompose:
const enhance = compose(connect(...), Filter)
export default enhance(SearchResult)
compose() wraps the components from right to left. Therefore, the leftmost higher-order component becomes the outermost one. This means the props will flow from left to right.
Please note that state.searchTerm in FilterComponent#generateList should be this.props.searchTerm.
What is 'state.searchTerm' in your wrapper function? I have a feeling you mean this.props.searchTerm. Also, you don't need an empty constructor in es6 classes. Also, this is work better done by a selector in your mapstatetoprops on the container.
Edit:
Also, you need to wrap the actual 'dumb' component, not the result of your connect call. That way your redux store is connected to your Filter component and will be rerendered when you're store changes.
generateList() is not reactive. It does not get triggered when the search term is changed.
SearchResults should be stateful and the container component. The list component should respond to change in the search term by receiving the search term as props. generateList should be the functionality of componentWillReceiveProps of the list component.

Categories

Resources