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.
Related
I have a question, if I can use useState generic in React Hooks, just like I can do this in React Components while managing multiple states?
state = {
input1: "",
input2: "",
input3: ""
// .. more states
};
handleChange = (event) => {
const { name, value } = event.target;
this.setState({
[name]: value,
});
};
Yes, with hooks you can manage complex state (without 3rd party library) in three ways, where the main reasoning is managing state ids and their corresponding elements.
Manage a single object with multiple states (notice that an array is an object).
Use useReducer if (1) is too complex.
Use multiple useState for every key-value pair (consider the readability and maintenance of it).
Check out this:
// Ids-values pairs.
const complexStateInitial = {
input1: "",
input2: "",
input3: ""
// .. more states
};
function reducer(state, action) {
return { ...state, [action.type]: action.value };
}
export default function App() {
const [fromUseState, setState] = useState(complexStateInitial);
// handle generic state from useState
const onChangeUseState = (e) => {
const { name, value } = e.target;
setState((prevState) => ({ ...prevState, [name]: value }));
};
const [fromReducer, dispatch] = useReducer(reducer, complexStateInitial);
// handle generic state from useReducer
const onChangeUseReducer = (e) => {
const { name, value } = e.target;
dispatch({ type: name, value });
};
return (
<>
<h3>useState</h3>
<div>
{Object.entries(fromUseState).map(([key, value]) => (
<input
key={key}
name={key}
value={value}
onChange={onChangeUseState}
/>
))}
<pre>{JSON.stringify(fromUseState, null, 2)}</pre>
</div>
<h3>useReducer</h3>
<div>
{Object.entries(fromReducer).map(([key, value]) => (
<input
name={key}
key={key}
value={value}
onChange={onChangeUseReducer}
/>
))}
<pre>{JSON.stringify(fromReducer, null, 2)}</pre>
</div>
</>
);
}
Notes
Unlike the setState method found in class components, useState does not automatically merge update objects. You can replicate this behavior by combining the function updater form with object spread syntax:
setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});
Refer to React Docs.
The correct way to do what you're trying to do is to create your own hook that uses useState internally.
Here is an example:
// This is your generic reusable hook.
const useHandleChange = (initial) => {
const [value, setValue] = React.useState(initial);
const handleChange = React.useCallback(
(event) => setValue(event.target.value), // This is the meaty part.
[]
);
return [value, handleChange];
}
const App = () => {
// Here we use the hook 3 times to show it's reusable.
const [value1, handle1] = useHandleChange('one');
const [value2, handle2] = useHandleChange('two');
const [value3, handle3] = useHandleChange('three');
return <div>
<div>
<input onChange={handle1} value={value1} />
<input onChange={handle2} value={value2} />
<input onChange={handle3} value={value3} />
</div>
<h2>States:</h2>
<ul>
<li>{value1}</li>
<li>{value2}</li>
<li>{value3}</li>
</ul>
</div>
}
ReactDOM.render(<App />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Note the use of React.useCallback to stop your hook from returning a new handler function on every render. (We don't need to specify setValue as a dependency because React guarantees that it will never change)
I didn't actually test this, but it should work.
See https://reactjs.org/docs/hooks-reference.html#usestate for more info.
import React, {useState} from 'react';
const MyComponent = () => {
const [name, setName] = useState('Default value for name');
return (<div><button onClick={()=>setName('John Doe')}}>Set Name</button></div>);
};
export default MyComponent;
I am having some issues with components being rerendered to often whenever the parent component has updated its state.
I have a simple Usercomponent which displays a name. The App component uses useState to store multiple users. Whenever the provided callback onClick is being run all components are rerendered, this decreases performance. I have tried using useCallback in order to prevent this from happening. The code can be run here. How can I prevent the other components being rerendered whenever the parent updates the users state?
const App = () => {
const [users, setUsers] = useState([
{ id: 1, name: "Cory" },
{ id: 2, name: "Meg" },
{ id: 3, name: "Bob" }
]);
const deleteUser = useCallback(id => {
setUsers(users => users.filter(user => user.id !== id));
}, []);
const renderUser = user => {
return <User key={user.id} user={user} onClick={deleteUser} />;
};
return (
<div>
<h1>Users</h1>
<ul>{users.map(renderUser)}</ul>
</div>
);
};
User
const User = props => {
const onDeleteClick = () => {
props.onClick(props.user.id);
};
console.log(`${props.user.name} just rendered`);
return (
<li>
<input type="button" value="Delete" onClick={onDeleteClick} />
{props.user.name}
</li>
);
};
use React.memo in your children component
export default React.memo(User);
you can use React.memo in User Component to stop re-rendering of the Component.
Working Link: https://codesandbox.io/s/no-arrow-func-render-eziql
Hope this helps!
I am trying to pass an id value (listId) from my store as props to my Component without any success. I think the problem is that the id is generated by the addList action, which is dispatched first, followed by the addTrack action. Neither listId={props.listId} or something like listId={props.listId ? props.listId : 1234} seem to work. Here is my code:
const MakeList = (props) => {
return (
<div>
<h1>Make a list </h1>
<ListForm
listId={props.listId}
onSubmit={(item) => {
props.dispatch(addList(item));
}}
trackSubmit={(item) => {
props.dispatch(addTrack(item, props.listId));
}}
/>
</div>
)
};
const mapStateToProps = (state) => {
return {
listId: state.userLists.listIdArray[index],
};
};
export default connect(mapStateToProps)(MakeList);
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.
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>