0

I have a loading component that creates a skeleton until the content renders via @trainline/react-skeletor. In this case I am creating a skeleton for a form.

First off, I have a CodeSandbox for those who want to see what is occurring, and all the Components used for a better idea of a solution.

I am also using a Function Based Component, and wish to keep it that way, unless this is impossible to do via a Function Based Component I do not want to use a Class Based Component to fix this issue.

I have a component ProfileForm that contains, at the moment, an h3 and a form

The form is as follows

const form = (
      <>
         <FormControl key={"profileForm"} submit={profileFormSubmit} form={profileFormData} validation={profileFormValidation}>
            <InputControl autoComplete="off" type="text" name="emailAddress" placeholder="Email address" label="Email Address">
               <ErrorMsg map="required" msg="Email is required"></ErrorMsg>
            </InputControl>
         </FormControl>
      </>
   )

FormControl Component returns a <form> element

InputControl Component returns a <label> and <input> element

ErrorMsg Component returns a <div>

Will render as the following.

<form class="Form " novalidate="">
  <div class="InputControl">
    <div>
      <label for="emailAddress">Email Address</label>
      <input type="text" placeholder="Email address" name="emailAddress" id="emailAddress" autocomplete="off" value=""></div>
      <div class="InputControl--Errors">
    </div>
  </div>
</form>

I have created a dummy http request where I update a state object with a title and the form above.

const [content, setContent] = useState();
const ttl = 500;
  /*simulate http request*/
  useEffect(() => {
    const timeout = setTimeout(() => {
      setContent({ title: "My Personal Details", form });
    }, ttl);
    return () => {
      clearTimeout(timeout);
    };
  }, []);

In my return, I pass down the content state object as a prop (The commented out code works but will not create the skeleton loading element that is required in my project

return (
    <div className="ProfileForm">
      <h2 style={{ color: "red" }}>Not working: Passing Form Down to Child</h2>
      <ProfileFormContainer content={content} />
      {/* <h2 style={{ color: 'green' }}>Working: Render Directly in return</h2>
        {form}*/}
    </div>
  );

ProfileFormContainer creates the skeleton element and then passes the props down into another Component and gets returned in the code snippet below.

const Wrapper = createSkeletonElement('div', 'Loader Loader--InlineBlock ProfileForm--loading');
const H3 = createSkeletonElement('h3', 'Loader Loader--InlineBlock');
const DIV = createSkeletonElement('div', 'Loader Loader--Block ');

const ProfileFormLoader = (props) => {

   return (
      <Wrapper className="ProfileForm">
         <H3 className="ProfileForm-title">{ props.title }</H3>
         <DIV>
            {props.form}
         </DIV>
      </Wrapper>
   );

}

export default ProfileFormLoader;

This renders as expected, however, when I try to type in the input, It does not update the value of the input. My question is, how do I update the value of an input, on input when the input is passed down as a prop to a child component as I have done?

Any help would be greatly appreciated

mcclosa
  • 943
  • 7
  • 29
  • 59

1 Answers1

1

Here is what happen: You basically only display content from useState

You simulate your httpRequest, which will update content to an empty form

And then you never update content ever again, it is still the empty form from the first render.

It works when you keep form out of content because form gets evaluated at every render with the actual profileFormData.

I suggest not using the state to store nodes in your case (and probably most other cases). The return of your http request should populate a data store that your template could read from, but given the fact that your form needs to read from other sources aswell, like the current input state, it is safer to keep the form in the render, where it will be updated at every render.

Bear-Foot
  • 744
  • 4
  • 12
  • That makes sense, thanks for clearing that up. I may have done this wrong, but I have put the form in the render, but it means it overlaps my loading component. I have this CodeSandbox to show what I mean - https://codesandbox.io/s/32ykv1xn26 Have I implemented what you said above incorrectly? – mcclosa Mar 26 '19 at 11:34
  • Yeah what you did works. Reason why your input stays visible is because your `.Loader` doens't hide it properly. You could add ` & input { border-color: none; border: none; background-color: transparent; &::placeholder { color: transparent; } }` or something cleaner ! – Bear-Foot Mar 26 '19 at 12:17
  • I actually just had to do some configuration when exporting `createSkeletonProvider` with `({ form, content }) => (form, content) === undefined,` and updating the dummy object with the correct structure. But thanks for pointing me in the correct direction, the css option could also work – mcclosa Mar 26 '19 at 12:25