I'm using SSR in Next.js. I'm trying to generate a unique ID in a component to use in a DOM element's id attribute. This component may be used multiple times on a page so each component instance needs its id to be unique. However, doing something like the following on each render of the component results in a server/client mismatch (component creates new ID on each render, so server and client do not match):
const gradientId = `linear-gradient-${uuid().slice(0, 8)}`
Stuff I've tried:
generating the ID as a default useState value
generating the ID right outside the component (same file, just above component definition)
generating the ID inside a useMemo
All seem to suffer from the client/server mismatch issue. Is there a good way to do this? Is there something stable in the component instance I can base the ID on instead (maybe React generates one I can use?).
I should also note I’m stuck on React 17 so I don’t have access to React 18’s useId, which seems aimed at solving this exact problem!
Any ideas appreciated - thanks!
Well, there may be a better solution out there, but the key appeared to be in abandoning a random ID in favour of a more deterministic ID like an index.
Outside of my component (in the same file, just above the component definition), I can define a function to generate the ID and I can seed it with 0.
let sequentialId = 0
const getSequentialId = function() {
return sequentialId++
}
function MyComponent({ ...otherProps }) {
...etc...
}
then, in my component, I can simply call this instead of the uuid() function I was previously calling:
const gradientId = `linear-gradient-${getSequentialId()}`
This forces the server render to generate 0, 1, 2, etc. and then when the client runs (remember client instances are brand new component instances), it will also start at 0 and sequentially assign IDs in the same order as the server.
I’ve gone one step further and moved this function to a provider where it can be called from any component in case I need this again:
const PageSettingsContext = React.createContext({
_sequentialId: 0,
getSequentialId: function() {
return this._sequentialId++
},
})
function usePageSettings() {
return useContext(PageSettingsContext);
}
function PageSettingsProvider({ ...otherProps }) {
return (
<PageSettingsContext.Provider>
<App />
</PageSettingsContext.Provider>
);
}
export {
PageSettingsProvider,
usePageSettings,
}
This seems to clear up the error and hopefully this holds up over time. Any other ideas or insights welcome but think this works for me.
I'm curious about passing props into setup and what are best practices to update variables/templates based on property changes.
I'm curious about reactive and computed.
For example:
setup(props) {
// Setup global config settings
const config = computed(() => {
return {
// See if the component is disabled
isDisabled: props.disabled, // (1)
// Test for rounded
isRounded: props.rounded // (2)
}
})
return { config }
}
Should config.isDisabled and config.isRounded be wrapped in their own computed function as they are both different and independent? However, it is easy to just stick them into one big function. What is best practice in this regard?
Does the entire config function evaluate once a single property changes within the function or can it recognize the change and update what is required?
Per docs, reactive is deeply reactive and used for objects, however, I've noticed it doesn't update to property changes. Therefore, I've been treating it more like data in Vue 2. Am I missing something or is this correct treatment?
You do not have to wrap props with computed at all, as they should be already reactive and immutable.
You also do not have to return config from your setup function as all props passed to your component should be automatically exposed to your template.
The computed function is evaluated only once and then Vue3 uses Proxy to observe changes to values and update only what's required. If you need to run a function every time a property changes you can use watchEffect.
Vue3 reactive is actually deep and works fine on objects. It should track all changes, unless you are trying to change the original object (the target of reactive function).
When creating a React app, if I use the hook useSelector, I need to adhere to the hooks invoking rules (Only call it from the top level of a functional component). If I use the mapStateToProps, I get the state in the props and I can use it anywhere without any issues... Same issue for useDispatch
What are the benefits of using the hook besides saving lines of code compared to mapStateToProps?
Redux store state can be read and changed from anywhere in the component, including callbacks. Whenever the store state is changed the component rerenders. When the component rerenders, useSelector runs again, and gives you the updated data, later to be used wherever you want. Here is an example of that and a usage of useDispatch inside a callback (after an assignment in the root level):
function Modal({ children }) {
const isOpen = useSelector(state => state.isOpen);
const dispatch = useDispatch();
function handleModalToggeled() {
// using updated data from store state in a callback
if(isOpen) {
// writing to state, leading to a rerender
dispatch({type: "CLOSE_MODAL"});
return;
}
// writing to state, leading to a rerender
dispatch({type: "OPEN_MODAL"});
}
// using updated data from store state in render
return (isOpen ? (
<div>
{children}
<button onClick={handleModalToggeled}>close modal</button>
</div>
) : (
<button onClick={handleModalToggeled}>open modal</button>
);
);
}
There is nothing you can do with mapStateToProps/mapDispatchToProps that you can't do with the useSelector and useDispatch hooks as well.
With that said, there are a couple of differences between the two methods that are worth considering:
Decoupling: with mapStateToProps, container logic (the way store data is injected into the component) is separate from the view logic (component rendering).
useSelector represents a new and different way of thinking about connected components, arguing that the decoupling is more important between components and that components are self contained. Which is better? Verdict: no clear winner. source
DX (Developer experience): using the connect function usually means there should be another additional container component for each connected component, where using the useSelector and useDispatch hooks is quite straightforward. Verdict: hooks have better DX.
"Stale props" and "Zombie child": there are some weird edge cases with useSelector, if it depends on props, where useSelector can run before the newest updated props come in. These are mostly rare and avoidable edge cases, but they had been already worked out in the older connect version. verdict: connect is slightly more stable than hooks. source
Performance optimizations: both support performance optimizations in different ways: connect has some advanced techniques, using merge props and other options hidden in the connect function. useSelector accepts a second argument - an equality function to determine if the state has changed. verdict: both are great for performance in advanced situations.
Types: using typescript with connect is a nightmare. I remember myself feverishly writing three props interfaces for each connected component (OwnProps, StateProps, DispatchProps). Redux hooks support types in a rather straightforward way. verdict: types are significantly easier to work with using hooks.
The future of React: Hooks are the future of react. This may seam like an odd argument, but change to the ecosystem is right around the corner with "Concurrent mode" and "Server components". While class components will still be supported in future React versions, new features may rely solely on hooks. This change will of course also affect third party libraries in the eco system, such as React-Redux. verdict: hooks are more future proof.
TL;DR - Final verdict: each method has its merits. connect is more mature, has less potential for weird bugs and edge cases, and has better separation of concerns. Hooks are easier to read and write, as they are collocated near the place where they are used (all in one self contained component). Also, they are easier to use with TypeScript. Finally, they will easily be upgradable for future react versions.
I think you misunderstand what "top level" is. It merely means that, inside a functional component, useSelector() cannot be placed inside loops, conditions and nested functions. It doesn't have anything to do with root component or components structure
// bad
const MyComponent = () => {
if (condition) {
// can't do this
const data = useSelector(mySelector);
console.log(data);
}
return null;
}
---
// good
const MyComponent = () => {
const data = useSelector(mySelector);
if (condition) {
console.log(data); // using data in condition
}
return null;
}
If anything, mapStateToPtops is located at even higher level than a hook call
the rules of hooks make it very hard to use that specific hook. You still need to somehow access a changing value from the state inside callbacks
To be fair you almost never have to access changing value inside a callback. I can't remember last time I needed that. Usually if your callback needs the latest state, you are better off just dispatching an action and then handler for that action (redux-thunk, redux-saga, redux-observable etc) will itself access the latest state
This is just specifics of hooks in general (not just useSelector) and there are tons of ways to go around it if you really want to, for example
const MyComponent = () => {
const data = useSelector(mySelector);
const latestData = useRef()
latestData.current = data
return (
<button
onClick={() => {
setTimeout(() => {
console.log(latestData.current) // always refers to latest data
}, 5000)
}}
/>
)
}
What are the benefits of using the hook besides saving lines of code compared to mapStateToProps?
You save time by not writing connect function any time you need to access store, and removing it when you no longer need to access store. No endless wrappers in react devtools
You have clear distinction and no conflicts between props coming from connect, props coming from parent and props injected by wrappers from 3rd party libraries
Sometimes you (or fellow developers you work with) would choose unclear names for props in mapStateToProps and you will have to scroll all the way to mapStateToProps in the file to find out which selector is used for this specific prop. This is not the case with hooks where selectors and variables with data they return are coupled on the same line
By using hooks you get general advantages of hooks, the biggest of which is being able couple together and reuse related stateful logic in multiple components
With mapStateToProps you usually have to deal with mapDispatchToProps which is even more cumbersome and easier to get lost in, especially reading someone else's code (object form? function form? bindActionCreators?). Prop coming from mapDispatchToProps can have same name as it's action creator but different signature because it was overridden in mapDispatchToprops. If you use one action creator in a number of components and then rename that action creator, these components will keep using old name coming from props. Object form easily breaks if you have a dependency cycle and also you have to deal with shadowing variable names
.
import { getUsers } from 'actions/user'
class MyComponent extends Component {
render() {
// shadowed variable getUsers, now you either rename it
// or call it like this.props.getUsers
// or change import to asterisk, and neither option is good
const { getUsers } = this.props
// ...
}
}
const mapDispatchToProps = {
getUsers,
}
export default connect(null, mapDispatchToProps)(MyComponent)
See EDIT 2 at the end for the final answer
Since no one knows how to answer, it seems like the best answer is that you should NOT be using useselector when you need information in other places other than the root level of your component. Since you don't know if the component will change in the future, just don't use useselector at all.
If someone has a better answer than this, I'll change the accepted answer.
Edit: Some answers were added, but they just emphasize why you shouldn't be using useselector at all, until the day when the rules of hooks will change, and you'll be able to use it in a callback as well. That being said, if you don't want to use it in a callback, it could be a good solution for you.
EDIT 2: An answer with examples of all that I wanted was added and showed how useSelector and useDispatch are easier to use.
The redux state returned from the useSelector hook can be passed around anywhere else just like its done for mapStateToProps. Example: It can be passed to another function too. Only constraint being that the hook rules has to be followed during its declaration:
It has to be declared only within a functional component.
During declaration, it can not be inside any conditional block . Sample code below
function test(displayText) {
return (<div>{displayText}</div>);
}
export function App(props) {
const displayReady = useSelector(state => {
return state.readyFlag;
});
const displayText = useSelector(state => {
return state.displayText;
});
if(displayReady) {
return
(<div>
Outer
{test(displayText)}
</div>);
}
else {
return null;
}
}
EDIT: Since OP has asked a specific question - which is about using it within a callback, I would like to add a specific code.In summary, I do not see anything that stops us from using useSelector hook output in a callback. Please see the sample code below, its a snippet from my own code that demonstrates this particular use case.
export default function CustomPaginationActionsTable(props) {
//Read state with useSelector.
const searchCriteria = useSelector(state => {
return state && state.selectedFacets;
});
//use the read state in a callback invoked from useEffect hook.
useEffect( ()=>{
const postParams = constructParticipantListQueryParams(searchCriteria);
const options = {
headers: {
'Content-Type': 'application/json'
},
validateStatus: () => true
};
var request = axios.post(PORTAL_SEARCH_LIST_ALL_PARTICIPANTS_URI, postParams, options)
.then(function(response)
{
if(response.status === HTTP_STATUS_CODE_SUCCESS) {
console.log('Accessing useSelector hook output in axios callback. Printing it '+JSON.stringify(searchCriteria));
}
})
.catch(function(error) {
});
}, []);
}
For callback functions you can use the value returned from useSelector the same way you would use the value from useState.
const ExampleComponent = () => {
// use hook to get data from redux state.
const stateData = useSelector(state => state.data);
// use hook to get dispatch for redux store.
// this allows actions to be dispatched.
const dispatch = useDispatch();
// Create a non-memoized callback function using stateData.
// This function is recreated every rerender, a change in
// state.data in the redux store will cause a rerender.
const callbackWithoutMemo = (event) => {
// use state values.
if (stateData.condition) {
doSomething();
}
else {
doSomethingElse();
}
// dispatch some action to the store
// can pass data if needed.
dispatch(someActionCreator());
};
// Create a memoized callback function using stateData.
// This function is recreated whenever a value in the
// dependency array changes (reference comparison).
const callbackWithMemo = useCallback((event) => {
// use state values.
if (stateData.condition) {
doSomething();
}
else {
doSomethingElse();
}
// dispatch some action to the store
// can pass data if needed.
dispatch(someActionCreator());
}, [stateData, doSomething, doSomethingElse]);
// Use the callbacks.
return (
<>
<div onClick={callbackWithoutMemo}>
Click me
</div>
<div onClick={callbackWithMemo}>
Click me
</div>
</>
)
};
Rules of hooks says you must use it at the root of your component, meaning you CANT use it anywhere.
As Max stated in his answer just means that the hook statement itself must not be dynamic / conditional. This is because the order of the base hooks (react's internal hooks: useState, etc) is used by the backing framework to populate the stored data each render.
The values from hooks can be used where ever you like.
While I doubt this will be close to answering your complete question, callbacks keep coming up and no examples had been posted.
not the answer but this hook can be very helpful if you want to get decoupled nature of mapDispatchToProps while keeping simplicity and dev experience of hooks:
https://gist.github.com/ErAz7/1bffea05743440d6d7559afc9ed12ddc
the reason I don't mention one for mapStatesToProps is that useSelector itself is more store-logic-decoupling than mapStatesToProps so don't see any advantage for mapStatesToProps. Of course I dont mean using useSelector directly but instead create a wrapper on it in your store files (e.g. in reducer file) and import from there, like this:
// e.g. userReducer.js
export const useUserProfile = () => useSelector(state => state.user.profile)
I've used higher order component to share a function between my components.With this implementation,the function comes as a prop in my component.The app supports multi languages so in each component a key is passed and the hash value is obtained to display. Hash values are passed to all the components using the context. Now getSkinHash access the context and returns the hash value.
const {getSkinHash} = this.props; //shared function,accesses the context
const value = getSkinHash(SOME_VALUE);
No problem with this implementation but getting the function out of prop every time leads to writing lot's of boilerplate code in all the components.
Is there a better/alternate ways to achieve this?
Thanks
React works with properties, so you can't just say you don't want to work with properties. That is when sharing data between components.
As far as you can do to shorten
const {getSkinHash} = this.props;
const value = getSkinHash(SOME_VALUE);
is to:
this.props.getSkinHash(SOME_VALUE).
If that is a generic function, not component dependent, you can choose to import it into your component just like you import other stuff.
import { myFunction } from './functions'
Then you would simple call it with myFunction.
If you need your function to synchronize data between your components, use a Redux action and connect your components to the global state. Your other components will get to know value hash changes too.
Let's assume I have an action that gets dispatched on page load, like say, in the index.js file.
example
store.dispatch(loadData());
in my reducer I initialize state to to an object. Something like this
function myReducer(state = {}, action)
now I have some smart component that subscribes to my state and then passes it down to another component to display the data. Another important note for this scenario is the fact that the retrieval of the data is happening asynchronously.
Let's also assume that the key of this object is some array.
So the markup component would have something like this
{this.props.object.key.map(k => do something)}
Now since key is undefined, if I call map on it, I blow up. The way I have been dealing with this, is by using a simple if check. If key is defined then run .map otherwise return null. Then by the time my data gets back from the server, the render will be called again due to a change in state that this component subscribed to. At this point the key is defined and map can be called.
Another approach, Is to define what your state will look like in the reducer. In other words, if I know that my state will be an object with an array property on it, I might do something like this.
const initialState = {
key:[]
}
function myReducer(state = initialState, action)
Doing this will benefit in the fact that now I won't need my if check since key is never undefined.
My questions is; are any of these approaches better than the other? Or perhaps, is there another way entirely to deal with this?
Generally, the approach I like to take is to define default props on the component which have the semantic meaning of "empty." For example, in context of the issue you describe I would typically structure my component like this (ES6 classes style):
class MyComponent extends React.Component {
static defaultProps = {
object: {
key: []
}
};
...
method() {
// this.props.object.key is an empty array, but is overriden
// with a value later after completion of the reducer
// (trigerred asynchronously)
this.props.object.key.map(doSomething);
}
}
This is relatively clean, prevents the code from throwing at run time, and forces you to create well-defined behaviors for semantically null, empty, or undefined input states.