When building tree structures in React, you might see this warning:
"Encountered two children with the same key..."
This usually happens when your component is rendering a list or tree and uses the same key
value for multiple items. React requires that keys be unique among siblings to correctly track changes.
In some apps, a tree of items is built using IDs like id
, targetId
, and parentId
. A common mistake is using a field like targetId
as the unique key — but if that field isn't truly unique, React throws an error.
Also, when building the tree structure, developers often assume that parentId
matches a node’s id
, but sometimes it actually matches others like targetId
— and since targetId
isn't unique, that causes lookup problems.
Here’s how to correctly build the tree and avoid duplicate keys:
id
as the unique key — it's meant to be unique and stable.id
.targetId
to nodes — since targetId
isn't unique, store an array of nodes per targetId
.parentId
to the corresponding targetId
, and attach the node to all matches (if multiple).Here’s a simplified version of the corrected tree builder:
function buildTree(nodes) {
const updatedNodes = nodes.map(node => ({
...node,
id: String(node.id),
targetId: String(node.targetId),
parentId: String(node.parentId),
children: [],
}));
const targetIdToNodes = {};
updatedNodes.forEach(node => {
if (!targetIdToNodes[node.targetId]) {
targetIdToNodes[node.targetId] = [];
}
targetIdToNodes[node.targetId].push(node);
});
const rootNodes = [];
updatedNodes.forEach(node => {
if (node.parentId === '0') {
rootNodes.push(node);
} else {
const parents = targetIdToNodes[node.parentId];
if (parents) {
parents.forEach(parent => {
parent.children.push(node);
});
}
}
});
return rootNodes;
}
In your React rendering, always use node.id
for the key
:
<TreeNode key={node.id} node={node} />
Always make sure your keys in React are unique and stable. And when building a tree, know what your parentId
is pointing to — it can make or break your structure.
JSX may look like HTML, but it’s actually JavaScript. When used correctly, it brings power and clarity to React development.
{}
for JavaScript expressions.className
instead of class
.<img />
.<div>
or <>
.Example:
function Greeting() {
return <h1>Hello, world!</h1>;
}
class
instead of className
.When using ref
in React, especially with forwardRef
, it's important to understand how ref
works — and how it doesn't.
You might see this warning in your console:
ref
is not a prop. Trying to access it will result inundefined
being returned...
This usually happens when you try to declare ref
in your component's propTypes
or access it like a normal prop (props.ref
). But ref
is a special prop in React. It doesn't behave like other props.
ref
When using forwardRef
, React gives the ref
as the second argument of your function. Here's what that looks like:
const MyComponent = forwardRef(function MyComponent(props, ref) {
// use ref directly
});
Don't try to access ref
through props.ref
. That won't work.
MyComponent.propTypes = {
// This will trigger a warning!
ref: PropTypes.any,
};
React doesn't pass ref
through the normal props
object, so it shouldn’t be validated with propTypes
.
ref
only via the second argument in forwardRef
.ref
from propTypes
.This will prevent the warning and adhere to React best practices.
If you're using TinyMCE in a React app and want to provide users with a way to remove all data-*
attributes from their content, you can create a custom toolbar button to do just that.
data-*
attributes are often added by frameworks, plugins, or tracking tools. In some cases, you might want to remove these before saving or publishing content, especially for clean, production-ready HTML.
You can use TinyMCE’s setup
function to add a custom button with a mop-like icon to your toolbar. Here's how:
import { Editor } from '@tinymce/tinymce-react';
function MyEditor({ content, contentChange, onEditorInit }) {
return (
<Editor
value={content}
onEditorChange={(newValue) => contentChange(newValue)}
onInit={onEditorInit}
init={{
height: 500,
menubar: false,
plugins: 'code',
toolbar: 'undo redo | mopCleaner | bold italic underline | code',
setup: (editor) => {
editor.ui.registry.addIcon('mopCleanerIcon', `
<svg width="24" height="24" viewBox="0 0 24 24">
<path d="M12 2v14" stroke="currentColor" stroke-width="2"/>
<path d="M8 16h8l1 4H7l1-4z" fill="currentColor"/>
</svg>
`);
editor.ui.registry.addButton('mopCleaner', {
icon: 'mopCleanerIcon',
tooltip: 'Remove all data-* attributes',
onAction: () => {
const currentContent = editor.getContent();
const cleanedContent = currentContent.replace(/\sdata-[\w-]+="[^"]*"/g, '');
editor.setContent(cleanedContent);
},
});
},
}}
/>
);
}
data-something="value"
.data-*
attributes are in a single tag.To support edge cases, such as attributes with single quotes or no values, you can enhance the regex as needed.
TinyMCE is a popular rich text editor used to add content-editing capabilities to web apps. When working with React, TinyMCE provides an official wrapper that makes integration quick and easy.
Here’s how to set it up in a React project:
Use npm:
npm install @tinymce/tinymce-react
Import the component and render it:
import { Editor } from '@tinymce/tinymce-react';
function MyEditor() {
return (
<Editor
initialValue="<p>Start typing...</p>"
init={{
height: 300,
menubar: false,
plugins: 'link image code',
toolbar: 'undo redo | formatselect | bold italic | link image | code',
}}
/>
);
}
This will render a fully functional rich text editor inside your React component.
You can capture changes to the editor content like this:
<Editor
onEditorChange={(newContent) => {
console.log(newContent); // Save or process the content here
}}
/>
TinyMCE is highly customizable with plugins, themes, and custom toolbars.
You can host TinyMCE locally or use the cloud version.
It supports custom buttons, icons, and content filtering.
Visit TinyMCE React Docs for advanced configuration options.
JavaScript Date
objects are always created and manipulated relative to the user’s local timezone, which it gets from the environment.
const date = new Date("2025-05-27T20:03:00Z"); // stored as UTC
console.log(date.toString());
// → "Tue May 27 2025 22:03:00 GMT+0200 (Central European Summer Time)"
To determine the client’s timezone:
Intl.DateTimeFormat().resolvedOptions().timeZone
// → "Europe/Berlin" or similar
This automatic timezone handling is key to understanding how UTC inputs appear offset when viewed or edited on the frontend.
To display datetimes in the user's local timezone while preserving backend UTC storage, you can use JavaScript's built-in timezone offset support:
const utcDate = "2025-05-27T20:03:00Z";
const localDisplay = new Date(utcDate).toLocaleString();
// → "5/27/2025, 10:03 PM" (depending on user's locale)
This gives users a familiar and correctly adjusted view of time. For a consistent format, Intl.DateTimeFormat
can be used.
ASP.NET Core with System.Text.Json
handles ISO 8601 UTC strings automatically when binding to DateTime
properties. Ensure you're not converting to UTC again if the incoming data already ends with Z
.
Best Practices:
[JsonPropertyName("created")]
public DateTime Created { get; set; } // Will be parsed as UTC if ends in "Z"
If needed, ensure correct serialization:
private readonly JsonSerializerOptions _jsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Converters = { new UtcDateTimeConverter() }
};
Custom converter (if required):
public class UtcDateTimeConverter : JsonConverter<DateTime>
{
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> DateTime.SpecifyKind(reader.GetDateTime(), DateTimeKind.Utc);
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
=> writer.WriteStringValue(value.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ"));
}
When a user selects a datetime in a <input type="datetime-local">
, the returned value (e.g., "2025-05-27T22:03"
) is in local time. To maintain UTC consistency on the backend, this must be converted to UTC.
Implementation:
const handleChange = (e) => {
const local = new Date(e.target.value); // local time
const utc = local.toISOString(); // UTC string for API
setPost({ ...post, created: utc });
};
This ensures accurate time data regardless of the user's timezone.
To populate an <input type="datetime-local">
in React, you must convert your UTC string into a local time string formatted as "yyyy-MM-ddTHH:mm"
— the only format the input accepts.
Implementation:
function toDatetimeLocalValue(dateInput) {
const date = new Date(dateInput);
const pad = (n) => n.toString().padStart(2, '0');
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
}
Use this function when binding input values in forms to ensure users see time in their own timezone.