How do I generate a stable unique ID in React with SSR? - javascript

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.

Related

How do I update state when a window variable changes in react

I have a fairly simple class component that needs access to some data from a service written in vanilla JS. It's simply an interface for the Web MIDI API, that must get access to the MIDI ports, then triggers a callback. I'm importing a function setMidiPorts to the MIDI service then calling it and sending the list of ports on MIDI success. I then need to render those ports in a drop down, but can't seem to get them updated in the component. I've tried passing them down as props from the parent, I've tried importing them directly. Nothing seems to work. I'm pretty new to react so I'm probably doing something pretty wrong, can anyone help me by pointing out where I'm going wrong.
window.inputs = [];
export const setMidiPorts = (inputs) => {
window.inputs = inputs;
console.log(inputs);
};
export default class Preferences extends Component {
constructor(props) {
super(props);
this.state = {
midiInputs: window.inputs,
midiOutputs: [],
};
}
......
EDIT -
I'm looking at this question to see if I can update state from outside, but don't understand how it works properly.
Update component state from outside React (on server response)
Thanks for everyones advice, I managed to solve it by following what it says here.
https://brettdewoody.com/accessing-component-methods-and-state-from-outside-react/
Adding this inside the component render
ref={(Preferences) => {window.Preferences = Preferences;}}
Then I was able to define the setMidiPorts function inside the component and call it from anywhere with window.Preferences.setMidiPorts

Unique storage for every function call inside React HOC

I want to create a helper that generates some data and saves it in some variable and on the next execution, it should use the memoized value for calculation.
Basically it's a helper for the High Order wrapper. It means that the storage should be created for every HOC but it shouldn't be re-created on the next re-render.
Now it looks like:
pseudo code
var storage; // I want to create this storage for every HOC only once.
function createDynamicStyles(theme, stylesCreator, status) {
// create a styles registry only once. This function can be re-called by the same HOC
// on each re-render so I want to use the memoized registry.
if (!storage) {
storage = stylesCreator(theme);
};
return storage[status];
}
const styleCreator = theme => ({
disabled: { color: theme.disabled },
success: { color: theme.success }
})
const Component_1 = componentHOC((props) => {
const { theme, status } = props;
// I'd like to keep this helper call as simple as possible. It already has 3 arguments.
const finalStyle = createDynamicStyles(theme, stylesCreator, status);
})(AwesomeComponent)
// these props can be changed during runtime
<Component_1 disabled={false} success={true} />
The functionality flow of this helper can be divided into 2 steps.
1) The first HOC call. It creates the styles based on the theme and saves them in the storage
2) Next Re-render of the HOC. It should fetch the previously created styles and return memoized value. This value should be unique for each HOC.
The problem is that the same helper can be used for other Components as well and it means that we can't use the same storage because it will be overwritten but the 'latest' HOC.
The possible ways how to solve it:
1) Create a class that will contain storage itself and creates a new Instance for each HOC.
To be honest, I'd like to avoid it because it looks too complicated for me in this case.
2) Create some Shared Registry and pass the UUID for every HOC.
It'd be nice but I don't know how to automatically do it. I don't want to manually pass the UUID on each HOC. I'd like to have this functionality under the hood to keep HOC calls, lightweight.
I was thinking about the new Map, and saving the created styles as Key-Value pair but it simply doesn't work as we don't have the generated KEY reference in the HOC. So we can't use it as a key.
Is it possible to do such a thing in the case of plain functions only?
Maybe I missed some other interesting variants.
Thanks for any help and suggestion.
Kind Regards.

React - proper state management for rows of unmounted JSX?

We have a crazy DOM hierarchy, and we've been passing JSX in props rather than embedding children. We want the base class to manage which documents of children are shown, and which children are docked or affixed to the top of their associated document's window.
List (crazy physics writes inline styles to base class wrappers)
Custom Form (passes rows of JSX to Base class)
Base Class (connects to list)
Custom Form (passes rows of JSX to base class)
Base class (connects to list)
The problem is that we're passing deeply nested JSX, and state management / accessing refs in the form is a nightmare.
I don't want to re-declare every row each time, because those rows have additional state attached to them in the Base Class, and the Base Class needs to know which rows actually changed. This is pretty easy if I don't redeclare the rows.
I don't know how to actually deal with rows of JSX in Custom Form.
Refs can only be appended in a subroutine of render(). What if CustomForm wants to measure a JSX element or write inline CSS? How could that JSX element exist in CustomForm.state, but also have a ref? I could cloneElement and keep a virtual DOM (with refs) inside of CustomForm, or depend on the base class to feed the deeply-nested, mounted ref back.
I believe it's bad practice to write component state from existing state. If CustomForm state changes, and I want to change which rows are passed to BaseClass, I have to throttle with shouldComponentUpdate, re-declare that stage document (maintaining row object references), then call setState on the overarching collection. this.state.stages.content[3].jsx is the only thing that changed, but I have to iterate through every row in every stage document in BaseClass when it sees that props.stages changed.
Is there some trick to dealing with collections of JSX? Am I doing something wrong? This all seems overly-complicated, and I would rather not worsen the problem by following some anti-pattern.
Custom Form:
render () {
return <BaseClass stages={this.stages()}/>
}
stages () {
if (!this._stages) this._stages = { title: this.title(), content: this.content() };
return this._stages;
}
title () {
return [{
canBeDocked: false,
jsx: (
<div>A title document row</div>
)
}
}
content () {
return [{
canBeDocked: false,
jsx: (
<div>Hello World</div>
)
}, {
canBeDocked: true,
jsx: (
<div>Yay</div>
)
}
}
What I usually do is just connect the lower level components via Redux. This helps with not passing the state in huge chunks from the top-most component.
A great video course by one of the React creators, Dan Abramov: Getting started with Redux
Absolutely agree with #t1gor. The answer for us was to use REDUX. It changed the entire game for us. Suddenly a button that is nested 10 levels deep (that is, inside a main view, header, header-container, left side grid, etc, etc, deeper and deeper) into purely custom components, has a chance to grab state whenever it needs.
Instead of...
Parent (pass down state) - owns state vars
Child (will pass down again) - parent has state vars
Grandchild (will pass down a third time) - grandparent has state vars
Great Grandchild (needs that state var) - great grandparent has state vars
You can do...
Parent (no passing) - reads global state vars
Child
Grandchild
Great Grandchild - also reads same global level state vars without being passed...
Usually the code looks something like this...
'use strict'
//Importation of Connection Tools & View
import { connect } from 'react-redux';
import AppView from './AppView';
//Mapping -----------------------------------
const mapStateToProps = state => {
return {
someStateVar: state.something.capturedInState,
};
}
const mapDispatchToProps = dispatch => {
return {
customFunctionsYouCreate: () => {
//do something!
//In your view component, access this by calling this.props.customFunctionsYouCreate
},
};
}
//Send Mappings to View...
export default connect(mapStateToProps, mapDispatchToProps)(AppView);
Long story short, you can keep all global app state level items in something called a store and whenever even the tiniest component needs something from app state, it can get it as the view is being built instead of passing.
The issue is having content as follows, and for some reason not being able to effectively persist the child instances that haven't changed (without re-writing the entire templateForChild).
constructor (props) {
super(props);
// --- can't include refs --->
// --- not subroutine of render --->
this.state = {
templateForChild: [
<SomeComponentInstance className='hello' />,
<AnotherComponentInstance className='world' />,
],
};
}
componentDidMount () {
this.setState({
templateForChild: [ <div className='sometimes' /> ],
}); // no refs for additional managing in this class
}
render () {
return ( <OtherManagerComponent content={this.state.templateForChild} /> );
}
I believe the answer could be to include a ref callback function, rather than a string, as mentioned by Dan Abramov, though I'm not yet sure if React does still throw a warning. This would ensure that both CustomForm and BaseClass are assigned the same ref instance (when props.ref callback is executed)
The answer is to probably use a key or createFragment. An unrelated article that addresses a re-mounting problem. Not sure if the fragment still includes the same instances, but the article does read that way. This is likely a purpose of key, as opposed to ref, which is for finding a DOM node (albeit findDOMNode(ref) if !(ref instanceof HTMLElement).

Sharing logic in react

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.

How to update Baobab leaf data before React component render?

ReactJS, Baobab, Material-UI app displays some items, identified by their numeric id. To display those, title and image url's are retrieved from a remote service via ajax. Tree branch stores that data:
data: {
12345: {title:'ABC', image:'https://...'}, // id is 12345
12346: {...
}
Upon item component creation and first rendering, its extended data may, or may not be already available in the tree. If its not, ajax call is enqueued to receive that data. It might happen that multiple items are created with the same item id.
To avoid extra requests for the same id, I want to put a dummy info {title:'loading', image:'spinner.gif'} into the tree upon the first request to that id's info. Thus this data will be used for the very first render(). Successive components would get that dummy info, and will not initiate any extra requests.
Question: how, and where can I place the code to test if the tree has no info yet and place the dummy there to indicate its "penging" state and enqueue the request?
Tried so far:
component's constructor – props are not set there yet;
componentWillMount() – the first render started with the old state of the tree, despite the tree.commit() after setting the dummy value;
in the branch function that dynamically creates components cursor pointing to its data. Got warning:
setState(...): Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state.
This can be solved one level up – once the list of ids is available. But it feels right that a component should be able to handle its data within itself.
Please advice a correct way to immediately update Baobab tree data before the first render of a React Component, from within that Component?
In my case (i am use same stack) wrap branch work fine.
import BaobabPropTypes from 'baobab-react/prop-types';
class Actions {
/**
* #param {Baobab} tree
*/
static prefetchTree = (tree) => {
tree.select(somePath).set(defaultValue);
tree.commit();
};
}
class Page extends React.Component {
static contextTypes = {
tree: BaobabPropTypes.baobab
};
componentWillMount() {
Actions.prefetchTree(this.context.tree);
}
render() {
return <Branch {...this.props}/>;
}
}
Baobab has a get event, use it to detect requests that return values that are not fetched yet:
tree.on('get', function(e) {
if (e.data.data === undefined) {
const path = e.data.path; // requested cursor path like ['data',12345]
const id = path[1];
FETCH_DATA(id)
.then( data => tree.set(path , data) );
tree.set(path, PLACEHOLDER_DATA);
}

Categories