Simulate inheritance using functional programming in React Redux application - reactjs

I have a component, I want to be able to override the render method and certain methods in that component. In React you cannot use inheritance. There could be a way using composition in functional programming, but how would you actually go about composing the render and all the methods into separate components. For React default component functions such as componentDidUpdate, would you be able to composition it as a separate component that you bring in to a higher-order component (HoC). How would props and state be passed through or accessed in each composed component of a HoC. It would be great to find an example of extending a component and overriding a method in some way.

"Simulate inheritance using functional progr-" Stop, what? Why would you elect such a burden for yourself?
Functional programming isn't about translating the concepts you know in other paradigms. You'll need to learn many new things before you can begin to write meaningful programs.
Here's some stuff from Brian Lonsdorf's React Rally 2016 presentation – it might show you what the pot at the end of the rainbow can look like, but getting there is thing all on its own.
Let functional style be new and different; leave old habits at the door.
const { withReducer } = Recompose
const Reducer = g =>
({
fold: g,
contramap: f =>
Reducer((state, action) => g(state, f(action))),
map: f =>
Reducer((state, action) => f(g(state, action))),
concat: o =>
Reducer((state, action) => o.fold(g(state, action), action))
})
const appReducer = Reducer((state, action) => {
switch (action.type) {
case 'set_visibility_filter':
return Object.assign({}, state, {
visibilityFilter: action.filter
})
default:
return state
}
})
const todoReducer = Reducer((state, action) => {
switch (action.type) {
case 'new_todo':
const t = { id: 0, title: action.payload.title }
return Object.assign({}, state, {
todos: state.todos.concat(t)
})
default:
return state
}
})
const Component = g =>
({
fold: g,
contramap: f =>
Component(x => g(f(x))),
concat: other =>
Component(x => <div>{g(x)} {other.fold(x)}</div>)
})
const classToFn = C =>
(props) => <C {...props} />
const Hoc = g =>
({
fold: g,
concat: other =>
Hoc(x => g(other.fold(x)))
})
// Example
// ======================
const todoApp = appReducer.concat(todoReducer)
.contramap(action => Object.assign({filter: 'all'}, action))
.map(s => Object.assign({}, s, {lastUpdated: Date()}))
const hoc = Hoc(withReducer('state', 'dispatch', todoApp.fold, {todos: []}))
const Todos = hoc.fold(({ state, dispatch }) =>
<div>
<span>Filter: {state.visibilityFilter}</span>
<ul>
{ state.todos.map((t, i) => <li key={i}>{t.title}</li>) }
</ul>
<button onClick={() =>
dispatch({ type: 'new_todo', payload: {title: 'New todo'}})}>
Add Todo
</button>
<button onClick={() =>
dispatch({ type: 'set_visibility_filter' })}>
Set Visibility
</button>
</div>
)
const TodoComp = Component(classToFn(Todos))
const Header = Component(s => <h1>Now Viewing {s}</h1>)
const ProfileLink = Component(u => <a href={`/users/${u.id}`}>{u.name}</a>)
const App = Header.contramap(s => s.pageName)
.concat(TodoComp)
.concat(ProfileLink.contramap(s => s.current_user))
.fold({ pageName: 'Home',
current_user: {id: 2, name: 'Boris' } })
ReactDOM.render(
App,
document.getElementById('container')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/recompose/0.26.0/Recompose.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>

Related

using createselector still rerenders component

I learn about redux and its features. One thing that I get trouble with is createselector in reduxtoolkit. I have a slice:
const titlesSlice = createSlice({
name: "title",
initialState: {
titles: [],
title: "",
},
reducers: {
addTitle: (state, action) => {
state.titles.push({
id: Math.trunc(Math.random() * 10000).toString(),
title: state.title,
});
},
titleChange: (state, action) => {
state.title = action.payload;
},
},
});
and a selectors like:
const getTitles = (state) => (state.titles.titles);
export const selectTitlesLengthWithReselect = createSelector(
[getTitles],
(titles) => titles.filter(elem => elem.title.length > 5))
In App.js I added input for adding title:
function App(props) {
const dispatch = useDispatch();
const title = useSelector((state) => state.titles.title);
return (
<div className="App">
<div>
<input type="text"
onChange={(e) => dispatch(titleChange(e.target.value))}
value={title} />
<button onClick={() => dispatch(addTitle())}>Save</button>
<Titlelist />
</div>
</div>
);
}
TitleList component:
const Titlelist = () => {
const allTitles = useSelector(selectTitlesLengthWithReselect);
console.log("RENDERED");
return (
<div>
{allTitles.map((elem) => (
<li key={elem.id}>{elem.title}</li>
))}
</div>
)
}
Problem is every time input value(title in the titleReducer) changes the TitleList component rerenders. But the data that comes from selector is memoized(I checked for the prev and current value equality and they are same). Is there something that I'm doing wrong or why does component rerenders?
Nothing wrong with your selector, it is memoized, the selector did not cause the re render (re creating of jsx).
Dispatching titleChange will re render App so it will re render Titlelist because it is not a pure component, you can make it a pure component with React.memo:
const Titlelist = React.memo(function TitleList() {
const allTitles = useSelector(
selectTitlesLengthWithReselect
);
console.log('RENDERED');
return (
<div>
{allTitles.map((elem) => (
<li key={elem.id}>{elem.title}</li>
))}
</div>
);
});
As phry commented; it is usually fine to let your component re create jsx as React will do a virtual DOM compare and not re render DOM if the jsx is the same, if you have handlers that are re created (like: onClick={()=>new ref}) then that will fail virtual DOM compare. You can use useCallback to prevent that.
A pure component will generate the same jsx reference if no props changed so will have a quicker virtual dom compare but will also take up more memory and setup processing so it may not always benefit your application.

redux useSelector: component is not reactive to state updates

I am setting up a very basic react typescript and redux app.
I used useSelector() to retrieve state then use it in my component.
however when I dipatch to the store adding a new article the ui doesn't change, I checked redux dev tools and the store is updated, I read that useSelector automatically subscribes to store so I'm not sure why I'm having this problem.
my App component code:
function App() {
const dispatch: Dispatch<any> = useDispatch();
const articles: readonly IArticle[] = useSelector(
(state: ArticleState) => state.articles
);
const saveArticle = React.useCallback(
(article: IArticle) => dispatch(addArticle(article)),
[dispatch]
);
return (
<div className="App">
<header className="App-header">
<h1>My Articles</h1>
<AddArticle saveArticle={saveArticle} />
<ul>
{articles.map((article: IArticle) => (
<li>{article.title}</li>
))}
</ul>
</header>
</div>
);
}
export default App;
the addArticle ActionCreator
export function addArticle(article: IArticle) {
const action: ArticleAction = {
type: actionTypes.ADD_ARTICLE,
article,
};
return simulateHttpRequest(action);
}
The Reducer
const reducer = (
state: ArticleState = initialState,
action: ArticleAction
): ArticleState => {
switch (action.type) {
case actionTypes.ADD_ARTICLE:
const newState = {
...state,
};
const newArticle: IArticle = {
id: Math.random(), // not really unique
title: action.article.title,
body: action.article.body,
};
newState.articles.push(newArticle);
return newState;
case actionTypes.REMOVE_ARTICLE:
const newArticles = state.articles.filter(
(article) => article.id !== action.article.id
);
return {
...state,
articles: newArticles,
};
default:
return state;
}
};
export default reducer;
here's a screenshot I see that data is actually updating in the store
The line newState.articles.push(newArticle); is mutating the existing articles array. Your selector is then trying to read state.articles. Since it's the same reference as before, React-Redux assumes nothing has changed, and will not re-render:
https://react-redux.js.org/api/hooks#equality-comparisons-and-updates
Please switch over to using Redux Toolkit for your store setup and reducer logic. Not only will it let you simplify your reducers by writing this kind of "mutating" logic and letting it create updates immutably, it effectively makes accidental mutations like this impossible.

What's the best way to use react-redux with list of items?

I have a list of items (JSON objects returned from API) in a redux store. This data is normalized currently, so it's just an array. It'll roughly have 10-30 objects and each object will have about 10 properties.
Currently we have a top level container (uses react-redux connect) that reads this list from the store, maps over the array and renders a component called ListItem, which basically needs 3-4 fields from the object to render the UI.
We don't have any performance issue with this now. But I wonder if it makes sense to have a redux container component for each list item? I think this will require data to be normalized and we'd need the unique id of each object to be passed to this container which can then read the object from redux store?
This question arises from the Redux docs' Style Guide - https://redux.js.org/style-guide/style-guide#connect-more-components-to-read-data-from-the-store
Just trying to understand which is the recommended way to use react-redux in this scenario.
Thanks!
I wonder if it makes sense to have a redux container component for each list item?
Besides possibly better performance there is also better code reuse. If the logic of what an item is is defined in the list then how can you reuse the list to render other items?
Below is an example where item is a combination of data and edit so props for item will be recreated for all items if you'd create the props in List instead of Item.
List can also not be used as a general list that passes id to Item component.
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { useMemo } = React;
const { createSelector } = Reselect;
const { produce } = immer;
const initialState = {
people: {
data: {
1: { id: 1, name: 'Jon' },
2: { id: 2, name: 'Marie' },
},
edit: {},
},
places: {
data: {
1: { id: 1, name: 'Rome' },
2: { id: 2, name: 'Paris' },
},
edit: {},
},
};
//action types
const SET_EDIT = 'SET_EDIT';
const CANCEL_EDIT = 'CANCEL_EDIT';
const SAVE = 'SAVE';
const CHANGE_TEXT = 'CHANGE_TEXT';
//action creators
const setEdit = (dataType, id) => ({
type: SET_EDIT,
payload: { dataType, id },
});
const cancelEdit = (dataType, id) => ({
type: CANCEL_EDIT,
payload: { dataType, id },
});
const save = (dataType, item) => ({
type: SAVE,
payload: { dataType, item },
});
const changeText = (dataType, id, field, value) => ({
type: CHANGE_TEXT,
payload: { dataType, id, field, value },
});
const reducer = (state, { type, payload }) => {
if (type === SET_EDIT) {
const { dataType, id } = payload;
return produce(state, (draft) => {
draft[dataType].edit[id] = draft[dataType].data[id];
});
}
if (type === CANCEL_EDIT) {
const { dataType, id } = payload;
return produce(state, (draft) => {
delete draft[dataType].edit[id];
});
}
if (type === CHANGE_TEXT) {
const { dataType, id, field, value } = payload;
return produce(state, (draft) => {
draft[dataType].edit[id][field] = value;
});
}
if (type === SAVE) {
const { dataType, item } = payload;
return produce(state, (draft) => {
const newItem = { ...item };
delete newItem.edit;
draft[dataType].data[item.id] = newItem;
delete draft[dataType].edit[item.id];
});
}
return state;
};
//selectors
const createSelectData = (dataType) => (state) =>
state[dataType];
const createSelectDataList = (dataType) =>
createSelector([createSelectData(dataType)], (result) =>
Object.values(result.data)
);
const createSelectDataById = (dataType, itemId) =>
createSelector(
[createSelectData(dataType)],
(dataResult) => dataResult.data[itemId]
);
const createSelectEditById = (dataType, itemId) =>
createSelector(
[createSelectData(dataType)],
(dataResult) => (dataResult.edit || {})[itemId]
);
const createSelectItemById = (dataType, itemId) =>
createSelector(
[
createSelectDataById(dataType, itemId),
createSelectEditById(dataType, itemId),
],
(item, edit) => ({
...item,
...edit,
edit: Boolean(edit),
})
);
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(() => (next) => (action) =>
next(action)
)
)
);
const Item = ({ item, dataType }) => {
const dispatch = useDispatch();
return (
<li>
{item.edit ? (
<React.Fragment>
<input
type="text"
value={item.name}
onChange={(e) =>
dispatch(
changeText(
dataType,
item.id,
'name',
e.target.value
)
)
}
/>
<button
onClick={() =>
dispatch(cancelEdit(dataType, item.id))
}
>
cancel
</button>
<button
onClick={() => dispatch(save(dataType, item))}
>
save
</button>
</React.Fragment>
) : (
<React.Fragment>
{item.name}
<button
onClick={() =>
dispatch(setEdit(dataType, item.id))
}
>
edit
</button>
</React.Fragment>
)}
</li>
);
};
const createItem = (dataType) =>
React.memo(function ItemContainer({ id }) {
const selectItem = useMemo(
() => createSelectItemById(dataType, id),
[id]
);
const item = useSelector(selectItem);
return <Item item={item} dataType={dataType} />;
});
const Person = createItem('people');
const Location = createItem('places');
const List = React.memo(function List({ items, Item }) {
return (
<ul>
{items.map(({ id }) => (
<Item key={id} id={id} />
))}
</ul>
);
});
const App = () => {
const [selectPeople, selectPlaces] = useMemo(
() => [
createSelectDataList('people'),
createSelectDataList('places'),
],
[]
);
const people = useSelector(selectPeople);
const places = useSelector(selectPlaces);
return (
<div>
<List items={people} Item={Person} />
<List items={places} Item={Location} />
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<script src="https://unpkg.com/immer#7.0.5/dist/immer.umd.production.min.js"></script>
<div id="root"></div>
If your application has repeating logic you may want to think of splitting the component up in container and presentation (container also called connected component in redux). You can re use the container but change the presentation.

trigger mapDispatchToProps but it does not change props [duplicate]

I am trying to display my state (users) in my react/redux functional component:
const Dumb = ({ users }) => {
console.log('users', users)
return (
<div>
<ul>
{users.map(user => <li>user</li>)}
</ul>
</div>
)
}
const data = state => ({
users: state
})
connect(data, null)(Dumb)
Dumb is used in a container component. The users.map statement has an issue but I thought that the data was injected through the connect statement? the reducer has an initial state with 1 name in it:
const users = (state = ['Jack'], action) => {
switch (action.type) {
case 'RECEIVED_DATA':
return action.data
default:
return state
}
}
CodeSandbox
You aren't using the connected component while rendering and hence the props aren't available in the component
const ConnectedDumb = connect(
data,
null
)(Dumb);
class Container extends React.Component {
render() {
return (
<div>
<ConnectedDumb />
</div>
);
}
}
Working demo

Why render is not triggered even the props changed

I have been using react+redux quite while, but could you any one help me the following case, on codepen:
const {createStore } = Redux;
const { Provider, connect } = ReactRedux;
const store = createStore((state={name: 'ron'}, action) => {
switch(action.type) {
case 'changeName': return {name: action.name};
default: return state
}
})
const Person = props => {
const {name, dispatch} = props
console.log(`rendering Person due to name changed to ${name}`)
return (
<div>
<p> My name is {name} </p>
<button onClick={ () => dispatch({type: 'changeName', name: 'ron'}) } > Change to Ron </button>
<button onClick={ () => dispatch({type: 'changeName', name: 'john'}) } > Change to John</button>
</div>
)
}
const App = connect(state=>state)(Person)
ReactDOM.render(
<Provider store={store}><App/></Provider>,
document.getElementById('root')
);
It is simple react app, but I cannot explain:
Initialise redux store with one reducer, and its initValue is {name: 'ron'}
Click Change to ron button, it will dispatch {type: 'changeName', name: 'ron'}
When the reducer get this action, it will generate an brand new state {name: 'ron'}, though the value is same as the original state, but they are different identity and should be the different ones.
The functional component should be re-rendered if the props changed even though the values are the same. So I suppose the render function will be called, and console should output rendering Person due to.... However, it is not happening.
I am wondering why react functional component refuse to render again when the props identity are changed (though the values are the same)
Your connect(state=>state)(Person) I think it's not wrong but it's weird.
According to the documentation https://redux.js.org/docs/basics/UsageWithReact.html you can separate the state and the action dispatcher, commonly naming mapStateToProps and mapDispatchToProps.
So, I propose to you this code:
const mapStateToProps = state => ({
user: state.user
})
const mapDispatchToProps = dispatch => ({
updateName: (name) => dispatch(changeName(name)),
})
class DemoContainer extends Component {
constructor() {
super();
}
render() {
return (
<div>
<p> My name is {this.props.user.name}</p>
<button onClick={ () => this.props.updateName('ron') } > Change to Ron </button>
<button onClick={ () => this.props.updateName('john') } > Change to John</button>
</div>
);
}
}
const Demo = connect(
mapStateToProps,
mapDispatchToProps
)(DemoContainer)
export default Demo
My reducer:
const initialState = { name: 'John'}
const user = (state = initialState, action) => {
switch (action.type) {
case "CHANGE_NAME":
return {
name: action.name
}
default:
return state
}
}
export default user
My action:
export const changeName = ( name ) => ({
type: "CHANGE_NAME",
name,
})
You can check all my code here: https://stackblitz.com/edit/react-tchqrg
I have a class for the component but you can also use a functional component with connect like you do.

Resources