简体   繁体   中英

React replace event.target with input

UPDATE Here's are some demos

contentEditable demo - requires double click for H1 to become editable

replace with input demo - adopts event.target styles but makes the UI 'twitch' when rendered


So I have some functional components, let's say:

component1.js

import React from 'react';
const component1 = props => (
<div>
 <h1>Title</h1>
</div>
);
export { component1 };

They are variable. event.target could be anything with text, so paragraph, heading, anything. I'm trying to let users edit content inline by clicking on it, so I'll pass a function editMode to these functional components, that'll update parent state with editing info, let's say like this:

<h1 onClick={event => {editMode(event, props.name, props.title, 'title')}}>title</h1>

This changes parent local state to have all the necessary information to grab the value from redux, define a target etc. For this example, props.name is the name of the component, props.title is the value, and 'title' is object key in redux.

So I'll add something to my component1.js and make it look a bit like this:

import React from 'react';
const component1 = props => (
    <div>
        {props.editState === 'true' &&
            <EditLayout
                name={props.name}
                target={props.target}
                value={props.value}
                onChange={event => someFunc(event)}
            />
        }
        <h1>Title</h1>
    </div>
    );
export { component1 };

Now this works fine, except it doesn't scale. EditLayout , in this case, will just return an input with correct value. What I need it to do is to adapt to whatever is being clicked, get font size, background, padding, margin, position. Am I doing this right? Every way I try, I run into huge issues:

Idea 1 - move EditLayout component outside of the functional component

Issue: positioning

So I'll move EditLayout to parent component that contains both component1.js and EditLayout . This will allow me to manipulate it from inside the functional component, without having to include it everywhere. I'll then grab coordinates and other important information from event.target like so:

const coords = event.target.getBoundingClientRect();
const offsetX = coords.left;
const offsetY = coords.top;
const childHeight = coords.height;
const childWidth = coords.width;
const childClass = event.target.className;

I'll then wrap the EditLayout to return a container which contains an input, and apply size/coordinates to the absolutely positioned container. This'll present an issue of input being offset by a random amount of pixels, depending on how big/where is the event.target .

Idea 2 - pass relevant computed styles to EditLayout

Issue: twitching on render, and I have to add EditLayout for every possible event.target there is, as well as condition its' render

So I'll grab all important computed styles like this:

const computedTarget = window.getComputedStyle(event.target);
const childMargins = computedTarget.marginBottom;
const childPaddings = computedTarget.padding;
const childFontSize = computedTarget.fontSize;
const childTextAlign = computedTarget.textAlign;

And pass it to component1.js , and then pass it to EditLayout component inside the component1.js. I'll then condition the component1.js. I'll then condition the event.target to hide if it's being edited like this:

<h1 className={ props.target === 'title' ? 'd-none' : ''}>Title</h1>

And condition the EditLayout to show only if it's needed:

{props.target === 'title' && <EditLayout />}

In this example, clicking h1 will show the input, but the layout itself with twitch on render. Input will have the exact same margin and font size as the h1, or event.target , but it'll appear bigger and extend the layout. Demo:

在此处输入图片说明

Idea 3 - Use conditional contentEditable

Issue: Requires double click to enable, doesn't work in safari, doesn't let me preselect the value

This is the weirdest of them all. I figured it'd be pretty simple, do something like this inside the functional component render:

<h1 contentEditable={props.target === 'title'} onClick={event => props.setTarget(event)}>Title</h1>

However, I have to double click to enable it. I have no idea why, if I attach a console log every time onClick is fired, I'll get correct outputs, I'll get the correct target value as well. I've tried numerous ways, but it simply requires double click. Even attempted to handle this inside the functional component, as most of the stuff is handled by a parent component, doesn't make a difference.

I have oversimplified the examples, so it's safe to assume/understand the following:

  • I am passing props in a correct fashion, they aren't undefined
  • I am using bootstrap
  • I am using styled components, and EditLayout is a styled component which accepts props and turns them into CSS like: font-size: ${props => props.fontSize};
  • The values should be correct, I am not manipulating anything I get back from getComputedStyle() or getBoundingClientRect()
  • I am keen on keeping my functional components functional, and easy to add. Functional components, in this case, are simple HTML structures, and I'd like to keep them as simple as possible

So there's a neat solution to contentEditable requiring two clicks instead of one, instead of binding onClick and passing it to enable contentEditable , simply keep contentEditable true and handle the change however you like. Here's a working h1 that doesn't require two clicks to enable contentEditable , unlike the one in the demo

<h1
    className="display-4 text-center"
    contentEditable
    suppressContentEditableWarning
    onBlur={event =>  updateValues(event)}
>
    Title 
</h1>

The available methods for trigger update could be onBlur or onInput .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM