Did I convert class component to function component correctly btw?
I don't know how to convert these code to function component tho, plz correct me
I don't necessarily need a code, just explain what I was wrong and what react knowledge I was missing
This is an original class component
const eventNames = ['onDragStart', 'onDrag', 'onDragEnd'];
function round5(value) {
return (Math.round(value * 1e5) / 1e5).toFixed(5);
}
export default class ControlPanel extends PureComponent {
renderEvent = eventName => {
const {events = {}} = this.props;
const lngLat = events[eventName];
return (
<div key={eventName}>
<strong>{eventName}:</strong> {lngLat ? lngLat.map(round5).join(', ') : <em>null</em>}
</div>
);
};
render() {
return (
<div className="control-panel">
<h3>Draggable Marker</h3>
<p>Try dragging the marker to another location.</p>
<div>{eventNames.map(this.renderEvent)}</div>
</div>
);
}
}
This is a code I converted to function component
function round5(value) {
return (Math.round(value * 1e5) / 1e5).toFixed(5);
}
const ControlPanel = (props) => {
const eventNames = ["onDragStart", "onDrag", "onDragEnd"];
function renderEvent(eventName) {
const { events } = props;
const lngLat = events[eventName];
return (
<div key={eventName}>
<strong>{eventName}:</strong>{" "}
{lngLat ? lngLat.map(round5).join(", ") : <em>null</em>}
</div>
);
}
return (
<div className="control-panel">
<h3>Draggable Marker</h3>
<p>Try dragging the marker to another location.</p>
<div>{eventNames.map(eventName => renderEvent(eventName))}</div>
</div>
);
};
export default ControlPanel;
you can reduce code a bit:
<div>{eventNames.map(renderEvent)}</div>
but renderEvent is not a react component so it has no this.props
You have to write renderEvent inside your functional component to access its props.
so in this case your component will be something like:
function round5(value) {
return (Math.round(value * 1e5) / 1e5).toFixed(5);
}
const ControlPanel = (props) => {
const renderEvent = (eventName) => {
const { events } = props;
const lngLat = events[eventName];
return (
<div key={eventName}>
<strong>{eventName}:</strong>{" "}
{lngLat ? lngLat.map(round5).join(", ") : <em>null</em>}
</div>
);
}
const eventNames = ["onDragStart", "onDrag", "onDragEnd"];
return (
<div className="control-panel">
<h3>Draggable Marker</h3>
<p>Try dragging the marker to another location.</p>
<div>{eventNames.map(eventName => renderEvent(eventName))}</div>
</div>
);
};
export default ControlPanel;
Related
I've a react component which includes a large function that updates the component state, the function is large so I want to move it to a separate file and export it in the react component. But I don't find anyway to access the component state if I move the function to its own file.
Is there anyway to do this ?
example:
component.tsx
import { myFunction } from './function.ts'
const [toggle, setToggle] = useState(false)
const my_component = () => {
return (
<div>
<button onClick={myFunction}>Run function</button>
</div>
)
}
export default my_component
function.ts
export const myFunction = () => {
// do something that updates `toggle`
}
you can do the logic apart from the component and return the result to the component. have a look at the code below.
https://codesandbox.io/s/hopeful-dubinsky-930p7?file=/src/App.js
This is just a raw example of what you can do with custom state hooks (reference: https://dev.to/spukas/react-hooks-creating-custom-state-hook-300c)
import React from 'react';
export function useMyFunction(value) {
const [toggle, setToggle] = React.useState(value || false);
const myFunction = () => {
// do something that updates `toggle` with setToggle(...)
}
return { toggle, myFunction };
}
import { useMyFunction } from './function.ts'
const my_component = () => {
const [toggle, myFunction] = useMyFunction(false)
return (
<div>
<button onClick={myFunction}>Run function</button>
</div>
)
}
export default my_component
This can be achieved by 2 different ways one using HOC components and another just by using functions.
Approach 1: Using HOC
handler.js
const withHandlers = (WrappedComponent) => {
class HandlerComponent extends Component {
state = {toggle:false};
myFunction = () => {
//Do your update here
}
render() {
return <WrappedComponent
toggle={this.state.toggle
myFunction={this.myFunction}
/>
}
};
my_component.js
const my_component = (props) => {
return (
<div>
<button onClick={props.myFunction}>Run function</button>
</div>
}
export default withHandlers(my_component);
Approach 2: Using Functions
handler.js
export const myFunction(toggle) => {
return !toggle; //return the changed value
}
my_component.js
const my_component = () => {
const [toggle, setToggle] = useState(false);
const myFunction = () => {
setToggle(handler.myFunction); //the state will be passed as a parameter by default
};
return(
<div>
<button onClick={myFunction}>Run function</button>
</div>
);
};
For the toggle to work, it must be passed to the function as a props then for update it used state management (redux or react context).
The best solution is to define the toggle in the function itself and pass it a Boolean props to control it.
import { myFunction } from './function.ts'
const my_component = () => {
return (
<div>
<button onClick={myFunction(false)}>Run function</button>
</div>
)
}
export default my_component
function.ts
export const myFunction = (props) => {
const [toggle, setToggle] = useState(props || false);
// your codes
};
I've seen examples of the useObserver hook that look like this:
const Test = () => {
const store = useContext(storeContext);
return useObserver(() => (
<div>
<div>{store.num}</div>
</div>
))
}
But the following works too, and I'd like to know if there's any reason not to use useObserver to return a value that will be used in render rather than to return the render.
const Test = () => {
const store = useContext(storeContext);
var num = useObserver(function (){
return store.num;
});
return (
<div>
<div>{num}</div>
</div>
)
}
Also, I don't get any errors using useObserver twice in the same component. Any problems with something like this?
const Test = () => {
const store = useContext(storeContext);
var num = useObserver(function (){
return store.num;
});
return useObserver(() => (
<div>
<div>{num}</div>
<div>{store.num2}</div>
</div>
))
}
You can use observer method in the component. And use any store you want.
import { observer } from "mobx-react-lite";
import { useStore } from "../../stores/StoreContext";
const Test = observer(() => {
const { myStore } = useStore();
return() => (
<div>
<div>{myStore.num}</div>
<div>{myStore.num2}</div>
</div>
)
}
);
StoreContext.ts
import myStore from './myStore'
export class RootStore{
//Define your stores here. also import them in the imports
myStore = newMyStore(this)
}
export const rootStore = new RootStore();
const StoreContext = React.createContext(rootStore);
export const useStore = () => React.useContext(StoreContext);
I am currently trying to use react-redux to subscribe to store changes for newsItems changes. My current component which is working but not listening for store changes as it should be looks like:
class Home extends Component {
state = { loading: false };
displayName = Home.name
render() {
let contents = this.renderContents(store.getState().newsItems);
return (
<div>
{contents}
</div>
);
}
renderContents = (newsItems) => {
var largeNewsItems = newsItems.splice(0, 2);
var firstNewsItem = largeNewsItems[0];
var secondNewsItem = largeNewsItems[1];
return (
<div>
<div>
<LargeNewsItem newsItem={firstNewsItem} />
</div>
<div>
<LargeNewsItem newsItem={secondNewsItem} />
</div>
</div>
);
}
}
export default Home;
when trying to update it to subscribe to teh store, I've made the following attempt:
class Home extends Component {
state = { loading: false };
displayName = Home.name
render(props) {
let contents = this.renderContents(props.newsItems);
return (
<div>
{contents}
</div>
);
}
renderContents = (newsItems) => {
var largeNewsItems = newsItems.splice(0, 2);
var firstNewsItem = largeNewsItems[0];
var secondNewsItem = largeNewsItems[1];
return (
<div>
<div>
<LargeNewsItem newsItem={firstNewsItem} />
</div>
<div>
<LargeNewsItem newsItem={secondNewsItem} />
</div>
</div>
);
}
}
const mapStateToProps = function(state) {
return {
newsItems: state.newsItems
}
}
export default connect(mapStateToProps)(Home);
which results in error:
TypeError: Cannot read property 'newsItems' of undefined
where I call props.newsItems.
What am I doing wrong here and how can I overcome it?
UPDATE:
it looks like I can overcome it by using:
`render() {
let contents = this.renderContents(this.props.newsItems);
return (
<div>
{contents}
</div>
);
}`
however my LargeNewsItem components will be passed null data once in a while. How can I overcome this and essentially "wait" until newsItems is populated
One approach could be to replace your render method as below .
`
render() {
const newsItems = this.props.newsItems;
if(!newsItems) {
return null;
}
let contents = this.renderContents(this.props.newsItems);
return (
<div>
{contents}
</div>
);
}`
this way if your newsitems is null you wont get an error and once the newsitems uddates your render method will be called again
I have the following code in react passes props from stateless component to state-full one and I get TypeError while running.
However, when I use props with same name the error goes away!
Your help would be appreciated in advance
import React, { Component } from 'react';
class App extends Component {
state = {
title:'xxxxxxx',
show:true,
SampleData:[object, object]
}
render() {
const {SampleData} = this.state.SampleData
return (
<div>
<SampleStateless list = {SampleData}/>
</div>
);
}
}
export default App;
const SampleStateless = (props) => {
const {list} = props
return (
<div>
<SampleStatefullComponent secondlist = {list} />
</div>
);
}
class SampleStatefullComponent extends Component {
state = {
something:''
}
render () {
const {secondlist} = this.props
console.log(secondlist);
// I get data correctly in console
const items = secondlist.map (item => return {some js})
//TypeError: secondlist is undefined
return (
<div>
{items}
</div>
)
}
}
You are doing map on a string but map works only on arrays. Take a look at corrected code
Also should be
const {SampleData} = this.state;
but not
const {SampleData} = this.state.SampleData;
Updated code
import React, { Component } from 'react';
class App extends Component {
state = {
title:'xxxxxxx',
show:true,
SampleData:[{'id': "01", "name": "abc"}, {'id': "02", "name": "xyz"}]
}
render() {
const {SampleData} = this.state;
return (
<div>
<SampleStateless list = {SampleData}/>
</div>
);
}
}
export default App;
const SampleStateless = (props) => {
const {list} = props
return (
<div>
<SampleStatefullComponent secondlist = {list} />
</div>
);
}
class SampleStatefullComponent extends Component {
state = {
something:''
}
render () {
const {secondlist} = this.props
console.log(secondlist);
// I get data correctly in console
const items = {secondlist && secondlist.map (item => item.name)}
return (
<div>
{items}
</div>
)
}
}
I am trying to test that a child component exists by shallow-rendering it with Enzyme. However, it does not appear to be getting past the return of the component and instead shallow-renders <undefined />.
MainComponentContainer.js
import PropTypes from 'prop-types'
import React from 'react'
import createReactClass from 'create-react-class'
import ImmutablePropTypes from 'react-immutable-proptypes'
import MainComponent from './MainComponent'
import {addSelection} from '../../actions/betslip'
import {connect} from 'react-redux'
export const MainComponentContainter = createReactClass({
displayName: 'MainComponentCont',
propTypes: {
displayMode: PropTypes.string,
other: ImmutablePropTypes.map,
addSelection: PropTypes.func,
prices: ImmutablePropTypes.map,
selections: ImmutablePropTypes.map,
},
render() {
return (
<div>
{this.props.other.valueSeq().map(this.renderMain)}
</div>
)
},
renderMain(other) {
const yesOutcome = other.get('markets').first().get('outcomes').first()
const newPrice = this.props.prices.getIn([yesOutcome.get('id'), 'price'])
if (newPrice) {
return (
<MainComponent key={other.get('id')}
...some other props/>
)
}
return null
}
})
const mapStateToProps = () => {
return (state) => {
const displayMode = state.ui.get('displayMode')
const selections = state.stp.get('selections')
const prices = state.catalog.get('prices')
const other = state.catalog.get('other')
return {
displayMode,
other,
prices,
selections,
}
}
}
const mapDispatchToProps = {
addSelection,
}
export default connect(mapStateToProps, mapDispatchToProps)(MainComponentContainer)
MainComponent.js
This just basically returns another component to the above component.
return (
<div style={[styles.container, styles.container[displayMode]]}>
<div style={[styles.logo, styles.logo[displayMode]]}>
{renderLogo(displayMode, quoteExtraLogo)}
{displayMode === 'mobile' &&
renderButton({displayMode, selected, suspended, clickHandler})}
</div>
<div style={[styles.content, styles.content[displayMode]]}>
<h2 style={[styles.headline, styles.headline[displayMode]]}>
{title}
</h2>
<div style={[styles.offer, styles.offer[displayMode]]}>
<div style={[styles.details, styles.details[displayMode]]}>
<p style={[styles.market, styles.market[displayMode]]}>
{text}
</p>
<div>
<p style={[styles.improvedOdds, styles.improvedOdds[displayMode]]}>
<span style={styles.improvedOddsAt}>a</span> {newPrice}
</p>
<p style={[styles.previousOdds, styles.previousOdds[displayMode]]}>
invece di{' '}
<span className="strikethrough">
{oldPrice}
</span>
</p>
</div>
</div>
{displayMode === 'desktop' &&
renderButton({displayMode, selected, suspended, clickHandler})}
</div>
</div>
</div>
)
Test
describe.only('MainComponentContainer Component', () => {
beforeEach(() => {
sandbox = sinon.sandbox.create()
addSelectionSpy = sinon.spy()
})
afterEach(() => {
sandbox.restore()
})
function getOutput({
displayMode = 'mobile',
other = mockData,
addSelection = spy,
prices = pricesMock,
selections = selectionsMock,
} = {}) {
return shallow(
<MainComponentContainer
displayMode = {displayMode}
other = {mockData}
addSelection = {addSelection}
prices = {prices}
selections = {selections}
/>
)
}
it('should include a MainComponent component', () => {
const pb = getOutput().find('MainComponent')
expect(pb.length).to.equal(1)
})
When doing the above test (should include a MainComponent component), I get the following error:
AssertionError: expected 0 to equal 1
+ expected - actual
-0
+1
However I have logged out getOutput().debug(), and it returns <div><undefined /></div>.
The shallow renderer is intentionally limited to operating on only the root component so as to make the test more isolated. In the case of decorators or "wrapped" components like this, the wrapped component is not what we want to test. Since MainComponentContainer is a HOC, you face this problem.
There are two ways to get around this problem, either
First export the undecorated component
export default connect(mapStateToProps, mapDispatchToProps)(MainComponentContainer)
export {MainComponentContainer as ComponentContainer};
and test like
return shallow(
<ComponentContainer
displayMode = {displayMode}
other = {mockData}
addSelection = {addSelection}
prices = {prices}
selections = {selections}
/>
)
or use .dive
it('should include a MainComponent component', () => {
const pb = getOutput().dive().find('MainComponent')
expect(pb.length).to.equal(1)
})