React onComponentDidMount event with react-redux connect - reactjs

In the below example, onComponentDidMount does not work because that event does not exist in React. Assuming I don't want to rewrite Main using React.Component, what event should I use, or is there another way?
let Main = ({myEventMethod}) => (
<main onComponentDidMount={myEventMethod}>
...
</main>
)
const mapDispatchToProps = (dispatch) => ({
myEventMethod: () => {...}
})
Main = connect(null, mapDispatchToProps)(Main)

DOCS:
Stateless Functions do not have the component lifecycle methods.

Higher Order Components come to rescue. Demo.
const withComponentDidMount = handler => Cmp => {
class WithComponentDidMount extends Component {
render() {
return <Cmp {...this.props}/>
}
}
WithComponentDidMount.prototype.componentDidMount = handler
return WithComponentDidMount
}
const Wrapped = withComponentDidMount(function() {
console.log('Mount')
})(App)

Related

Convert functional component from class component

I want to convert my class component to functional component and t is difficult for me to convert render() of class component.
Here is my code sandbox link. https://codesandbox.io/s/snowy-smoke-s65vx4?file=/src/App.js
Can anyone please help me with this
The render part is just going to be the body of the function and the return.
const myfunccomp : React.FC = ({id, info, msgList}) => {
const chatContentHeader = () => {...}
const chatContentBody = () => {...}
const chatContentFooter = () => {...}
...
return (
<div className="chat-content">
{chatContentHeader(info.name)}
{chatContentBody(msgList, id)}
{chatContentFooter()}
</div>
);
}

convert function components to class components

I have this class component here
class App extends React.Component {
getHowler() {
this.player.howler;
}
getDuration() {
this.player.duration();
}
getSeek() {
this.player.seek();
}
setSeek() {
this.player.seek(0.5);
}
// This sound file may not work due to cross-origin setting
render() {
return (
<ReactHowler
src="http://goldfirestudios.com/proj/howlerjs/sound.ogg"
playing={true}
ref={(ref) => (this.player = ref)}
/>
);
}
}
I want this to convert into function components. I want to use react howler seek function in function components. How can I do that? I tried useRef from react and it throws me an error.
The functionality should work like this:
every time I swap the tract it should play from the beginning
const App = () => {
const player = useRef(null)
const getHowler = () => {
player.current.howler;
}
const getDuration = () => {
player.current.duration();
}
const getSeek () => {
player.current.seek();
}
const setSeek = () => {
player.current.seek(0.5);
}
render() {
return (
<ReactHowler
src="http://goldfirestudios.com/proj/howlerjs/sound.ogg"
playing={true}
ref={player}
/>
);
}
}
You might need to return asap when player.current === null
You can also wrap the component in the Error boundary component
If you want to use complete functional component already used in production, feel free to use the package I wrote a package named atpro-howler which is howler integrated with react via react-aptor

React child component does not receive props on first load

I am fetching data in parent 'wrapper' component and pass it down to two child components. One child component receives it well, another does not.
In container:
const mapStateToProps = createStructuredSelector({
visitedCountriesList: getVisitedCountriesList(),
visitedCountriesPolygons: getVisitedCountriesPolygons()
});
export function mapDispatchToProps(dispatch) {
return {
loadVisitedCountries: () => {
dispatch(loadVisitedCountriesRequest())
},
};
}
in redux-saga I fetch data from API and store them:
function mapPageReducer(state = initialState, action) {
switch (action.type) {
case FETCH_VISITED_COUNTRIES_SUCCESS:
return state
.setIn(['visitedCountriesPolygons', 'features'], action.polygons)
}
Selectors:
const getVisitedCountriesList = () => createSelector(
getMapPage,
(mapState) => {
let countriesList = mapState.getIn(['visitedCountriesPolygons', 'features']).map(c => {
return {
alpha3: c.id,
name: c.properties.name
}
});
return countriesList;
}
)
const getVisitedCountriesPolygons = () => createSelector(
getMapPage,
(mapState) => mapState.get('visitedCountriesPolygons')
)
in a wrapper component I render two components, triggering data fetch and passing props down to child components (visitedCountriesPolygons and visitedCountriesList):
class MapView extends React.Component {
constructor(props) {
super(props)
this.props.loadVisitedCountries();
}
render() {
return (
<div>
<Map visitedCountriesPolygons={this.props.visitedCountriesPolygons} />
<MapActionsTab visitedCountriesList={this.props.visitedCountriesList} />
</div>
);
}
}
Then, in first child component Map I receive props well and can build a map:
componentDidMount() {
this.map.on('load', () => {
this.drawVisitedPolygons(this.props.visitedCountriesPolygons);
});
};
But in the second component MapActionsTab props are not received at initial render, but only after any update:
class MapActionsTab extends React.Component {
constructor(props) {
super(props);
}
render() {
let countriesList = this.props.visitedCountriesList.map(country => {
return <li key={country.alpha3}>{country.name}</li>;
}) || '';
return (
<Wrapper>
<div>{countriesList}</div>
</Wrapper>
);
}
}
UPD:
Saga to fetch data form API:
export function* fetchVisitedCountries() {
const countries = yield request
.get('http://...')
.query()
.then((res, err) => {
return res.body;
});
let polygons = [];
yield countries.map(c => {
request
.get(`https://.../${c.toUpperCase()}.geo.json`)
.then((res, err) => {
polygons.push(res.body.features[0]);
})
});
yield put(fetchVisitedCountriesSuccess(polygons));
}
and a simple piece of reducer to store data:
case FETCH_VISITED_COUNTRIES_SUCCESS:
return state
.setIn(['visitedCountriesPolygons', 'features'], action.polygons)
Why is it different and how to solve it, please?
thanks,
Roman
Apparently, this works correct and it was just a minor issue in another place (not pasted here and not errors reported).
After thorough clean up and refactoring it worked as expected.
Conclusion: always keep your code clean, use linter and follow best practices :)
I think the problem may be in your selectors, in particular this one, whose component parts being executed immediately (with no fetched data values), and hence values will not change as it is memoized. This means that it will not cause an update to the component should the the underlying data change from the fetched data
const mapStateToProps = createStructuredSelector({
visitedCountriesList: getVisitedCountriesList, // should not execute ()
visitedCountriesPolygons: getVisitedCountriesPolygons // should not execute ()
});
By not executing the composed selectors immediately, mapStateToProps will call them each time the state changes and they should select the new values and cause an automatic update of your react component

React assign key to already rendered component

Is it possible?
I have a component where children are rendered by an arbitrary mapping function coming in as props. A simplified example:
class SomeComponent extends Component {
render() {
const { renderChild, businessObjects } = this.props
return <div>
{businessObjects.map(renderChild)}
</div>
}
}
I obviously get a warning saying children are rendered without the key attribute.
I tried assigning the key after the vdom element is rendered:
...
{
businessObjects.map(e => {
const vdom = renderChild(e)
vdom.key = e.id
return vdom
})
}
...
But the object returned from the JSX transform is frozen, so I can't do this. Also there is no API to temporarily unfreeze then re-freeze objects in js. Cloning is out of question for performance reasons (thousands of components are rendered like this)
What can I do?
Again, for performance reason I can't wrap the rendered children into another component, so a solution like this wouldn't work:
const Child = ({renderChild, bo}) => (<div>{renderChild(bo)}</div>)
// in SomeComponent
...
{
businessObjects.map(e => (<Child
key={e.id}
bo={e}
renderChild={renderChild}
/>)
)
}
...
Update
The reason for this structure is that SomeComponent is a dumb component, and has no access to application state (redux). But the rendered children do need to have access to dispatch (I do it in a form of connected action creators).
So you can imagine the whole thing like this:
const createChildRenderer = ({actionFoo, actionBar}) => (obj) => {
switch(obj.type) {
case FOO:
return <div onClick={() => actionFoo()}>{obj.text}</div>
case BAR:
return <div onClick={() => actionBar()}>{obj.text}</div>
default:
return null
}
}
And in a connected component
#connect(
({ businessObjects }) => { businessObjects },
{ actionFoo, actionBar}
)
class SmartComponent extends Component {
render() {
const renderChild = createChildRenderer({
actionFoo: this.props.actionFoo, // action creators
actionBar: this.props.actionBar
})
return (<SomeComponent
renderChild={renderChild}
businessObjects={this.props.businessObjects}>
}
}
The way I ended up solving this by taking an actual react component as an argument:
So that in the dumb component that previously took a renderer function, now I take a component:
class SomeComponent extends Component {
render() {
const { ChildComponent, businessObjects } = this.props
return <div>
{businessObjects.map((o) => (<ChildComponent
businessObject={o}
key={o.id}
/>)}
</div>
}
}
And where I previously created the renderer function, now I create the component:
const createChildComponent = ({actionFoo, actionBar}) =>
({ businessObject: obj }) => { // this is now a component created dynamically
switch(obj.type) {
case FOO:
return <div onClick={() => actionFoo()}>{obj.text}</div>
case BAR:
return <div onClick={() => actionBar()}>{obj.text}</div>
default:
return null
}
}
And in the connected component:
#connect(
({ businessObjects }) => { businessObjects },
{ actionFoo, actionBar}
)
class SmartComponent extends Component {
render() {
const ChildComponent = createChildComponent({
actionFoo: this.props.actionFoo, // action creators
actionBar: this.props.actionBar
})
return (<SomeComponent
ChildComponent={ChildComponent}
businessObjects={this.props.businessObjects}>
}
}
You can use cloneElement on the child received from renderChild:
React.cloneElement(
child,
{...child.props, key: yourKeyValue}
)

ReactJS lifecycle method inside a function Component

Instead of writing my components inside a class, I'd like to use the function syntax.
How do I override componentDidMount, componentWillMount inside function components?
Is it even possible?
const grid = (props) => {
console.log(props);
let {skuRules} = props;
const componentDidMount = () => {
if(!props.fetched) {
props.fetchRules();
}
console.log('mount it!');
};
return(
<Content title="Promotions" breadcrumbs={breadcrumbs} fetched={skuRules.fetched}>
<Box title="Sku Promotion">
<ActionButtons buttons={actionButtons} />
<SkuRuleGrid
data={skuRules.payload}
fetch={props.fetchSkuRules}
/>
</Box>
</Content>
)
}
Edit: With the introduction of Hooks it is possible to implement a lifecycle kind of behavior as well as the state in the functional Components. Currently
Hooks are a new feature proposal that lets you use state and other
React features without writing a class. They are released in React as a part of v16.8.0
useEffect hook can be used to replicate lifecycle behavior, and useState can be used to store state in a function component.
Basic syntax:
useEffect(callbackFunction, [dependentProps]) => cleanupFunction
You can implement your use case in hooks like
const grid = (props) => {
console.log(props);
let {skuRules} = props;
useEffect(() => {
if(!props.fetched) {
props.fetchRules();
}
console.log('mount it!');
}, []); // passing an empty array as second argument triggers the callback in useEffect only after the initial render thus replicating `componentDidMount` lifecycle behaviour
return(
<Content title="Promotions" breadcrumbs={breadcrumbs} fetched={skuRules.fetched}>
<Box title="Sku Promotion">
<ActionButtons buttons={actionButtons} />
<SkuRuleGrid
data={skuRules.payload}
fetch={props.fetchSkuRules}
/>
</Box>
</Content>
)
}
useEffect can also return a function that will be run when the component is unmounted. This can be used to unsubscribe to listeners, replicating the behavior of componentWillUnmount:
Eg: componentWillUnmount
useEffect(() => {
window.addEventListener('unhandledRejection', handler);
return () => {
window.removeEventListener('unhandledRejection', handler);
}
}, [])
To make useEffect conditional on specific events, you may provide it with an array of values to check for changes:
Eg: componentDidUpdate
componentDidUpdate(prevProps, prevState) {
const { counter } = this.props;
if (this.props.counter !== prevState.counter) {
// some action here
}
}
Hooks Equivalent
useEffect(() => {
// action here
}, [props.counter]); // checks for changes in the values in this array
If you include this array, make sure to include all values from the component scope that change over time (props, state), or you may end up referencing values from previous renders.
There are some subtleties to using useEffect; check out the API Here.
Before v16.7.0
The property of function components is that they don't have access to Reacts lifecycle functions or the this keyword. You need to extend the React.Component class if you want to use the lifecycle function.
class Grid extends React.Component {
constructor(props) {
super(props)
}
componentDidMount () {
if(!this.props.fetched) {
this.props.fetchRules();
}
console.log('mount it!');
}
render() {
return(
<Content title="Promotions" breadcrumbs={breadcrumbs} fetched={skuRules.fetched}>
<Box title="Sku Promotion">
<ActionButtons buttons={actionButtons} />
<SkuRuleGrid
data={skuRules.payload}
fetch={props.fetchSkuRules}
/>
</Box>
</Content>
)
}
}
Function components are useful when you only want to render your Component without the need of extra logic.
You can use react-pure-lifecycle to add lifecycle functions to functional components.
Example:
import React, { Component } from 'react';
import lifecycle from 'react-pure-lifecycle';
const methods = {
componentDidMount(props) {
console.log('I mounted! Here are my props: ', props);
}
};
const Channels = props => (
<h1>Hello</h1>
)
export default lifecycle(methods)(Channels);
You can make your own "lifecycle methods" using hooks for maximum nostalgia.
Utility functions:
import { useEffect, useRef } from "react";
export const useComponentDidMount = handler => {
return useEffect(() => handler(), []);
};
export const useComponentDidUpdate = (handler, deps) => {
const isInitialMount = useRef(true);
useEffect(() => {
if (isInitialMount.current) {
isInitialMount.current = false;
return;
}
return handler();
}, deps);
};
export const useComponentWillUnmount = handler => {
return useEffect(() => handler, []);
};
Usage:
import {
useComponentDidMount,
useComponentDidUpdate,
useComponentWillUnmount
} from "./utils";
export const MyComponent = ({ myProp }) => {
useComponentDidMount(() => {
console.log("Component did mount!");
});
useComponentDidUpdate(() => {
console.log("Component did update!");
});
useComponentDidUpdate(() => {
console.log("myProp did update!");
}, [myProp]);
useComponentWillUnmount(() => {
console.log("Component will unmount!");
});
return <div>Hello world</div>;
};
Solution One:
You can use new react HOOKS API. Currently in React v16.8.0
Hooks let you use more of React’s features without classes.
Hooks provide a more direct API to the React concepts you already know: props, state, context, refs, and lifecycle.
Hooks solves all the problems addressed with Recompose.
A Note from the Author of recompose (acdlite, Oct 25 2018):
Hi! I created Recompose about three years ago. About a year after
that, I joined the React team. Today, we announced a proposal for
Hooks. Hooks solves all the problems I attempted to address with
Recompose three years ago, and more on top of that. I will be
discontinuing active maintenance of this package (excluding perhaps
bugfixes or patches for compatibility with future React releases), and
recommending that people use Hooks instead. Your existing code with
Recompose will still work, just don't expect any new features.
Solution Two:
If you are using react version that does not support hooks, no worries, use recompose(A React utility belt for function components and higher-order components.) instead. You can use recompose for attaching lifecycle hooks, state, handlers etc to a function component.
Here’s a render-less component that attaches lifecycle methods via the lifecycle HOC (from recompose).
// taken from https://gist.github.com/tsnieman/056af4bb9e87748c514d#file-auth-js-L33
function RenderlessComponent() {
return null;
}
export default lifecycle({
componentDidMount() {
const { checkIfAuthed } = this.props;
// Do they have an active session? ("Remember me")
checkIfAuthed();
},
componentWillReceiveProps(nextProps) {
const {
loadUser,
} = this.props;
// Various 'indicators'..
const becameAuthed = (!(this.props.auth) && nextProps.auth);
const isCurrentUser = (this.props.currentUser !== null);
if (becameAuthed) {
loadUser(nextProps.auth.uid);
}
const shouldSetCurrentUser = (!isCurrentUser && nextProps.auth);
if (shouldSetCurrentUser) {
const currentUser = nextProps.users[nextProps.auth.uid];
if (currentUser) {
this.props.setCurrentUser({
'id': nextProps.auth.uid,
...currentUser,
});
}
}
}
})(RenderlessComponent);
componentDidMount
useEffect(()=>{
// code here
})
componentWillMount
useEffect(()=>{
return ()=>{
//code here
}
})
componentDidUpdate
useEffect(()=>{
//code here
// when userName state change it will call
},[userName])
According to the documentation:
import React, { useState, useEffect } from 'react'
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
});
see React documentation
Short and sweet answer
componentDidMount
useEffect(()=>{
// code here
})
componentWillUnmount
useEffect(()=>{
return ()=>{
//code here
}
})
componentDidUpdate
useEffect(()=>{
//code here
// when userName state change it will call
},[userName])
You can make use of create-react-class module.
Official documentation
Of course you must first install it
npm install create-react-class
Here is a working example
import React from "react";
import ReactDOM from "react-dom"
let createReactClass = require('create-react-class')
let Clock = createReactClass({
getInitialState:function(){
return {date:new Date()}
},
render:function(){
return (
<h1>{this.state.date.toLocaleTimeString()}</h1>
)
},
componentDidMount:function(){
this.timerId = setInterval(()=>this.setState({date:new Date()}),1000)
},
componentWillUnmount:function(){
clearInterval(this.timerId)
}
})
ReactDOM.render(
<Clock/>,
document.getElementById('root')
)
if you using react 16.8 you can use react Hooks...
React Hooks are functions that let you “hook into” React state and lifecycle features from function components...
docs
import React, { useState, useEffect } from "react";
const Counter = () => {
const [count, setCount] = useState(0);
const [count2, setCount2] = useState(0);
// componentDidMount
useEffect(() => {
console.log("The use effect ran");
}, []);
// // componentDidUpdate
useEffect(() => {
console.log("The use effect ran");
}, [count, count2]);
// componentWillUnmount
useEffect(() => {
console.log("The use effect ran");
return () => {
console.log("the return is being ran");
};
}, []);
useEffect(() => {
console.log(`The count has updated to ${count}`);
return () => {
console.log(`we are in the cleanup - the count is ${count}`);
};
}, [count]);
return (
<div>
<h6> Counter </h6>
<p> current count: {count} </p>
<button onClick={() => setCount(count + 1)}>increment the count</button>
<button onClick={() => setCount2(count2 + 1)}>increment count 2</button>
</div>
);
};
export default Counter;

Resources