Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Render Not Displaying HTML #173

Closed
donadley opened this issue Jun 1, 2016 · 11 comments
Closed

Render Not Displaying HTML #173

donadley opened this issue Jun 1, 2016 · 11 comments
Labels

Comments

@donadley
Copy link

donadley commented Jun 1, 2016

I added a Component that changes the page depending on what is in the view property like the one below. When updateView is called it changes what is in the view.

import {
    h,
    render,
    Component
} from 'preact';
import Home from './home';
import Status from './Status';

let view;
class Pages extends Component {
    constructor() {
        super();
        view = <Status />
    }
    updateView(redirect, state) {
        switch (redirect) {
            case "HOME":
                view = <Home state={state} />
                break;
                }
    getView() {
        return view;
    }
    reset() {
        view = <Status />;
    }
}
const PagesInstance = new Pages();
export default PagesInstance;

The below class is updated and it shows whatever is in in the view.

import {
    h,
    render,
    Component
} from 'preact';
import objectAssign from 'object-assign';
import Login from './login';
let viewReference = null;
export default class AuthenticatedRoute extends Component {
    constructor() {
        super();
        this.onChange = this.onChange.bind(this);
        this.setState(AuthStore.getState());
    }
    componentDidMount() {
        AuthStore.addChangeListener(this.onChange);
        UserStore.addChangeListener(this.onChange);
    }
    componentWillUnmount() {
        AuthStore.removeChangeListener(this.onChange);
        UserStore.removeChangeListener(this.onChange);
    }
    onChange() {
        this.setState(AuthStore.getState());
    }
    render() {
        if (!AuthStore.getState().loggedIn) {
            return <Login />;
        } else {
            if (UserStore.getView() != viewReference) { 
                viewReference = objectAssign({}, UserStore.getView());
                return UserStore.getView();
            }
        }
    }
}

The issue is that even though most of the other Components display, when someone log out the rendered JSX or even just a div doesn't display. At the beginning it work just fine. The console even logs that it is constructed and rendered after the update has occured, but nothing shows and there is not a div. Can you give an any reason why this is happening? Below are the rest of the classes just in case they may be useful.

import {
    h,
    render,
    Component
} from 'preact';

export default class Login extends Component {
    constructor() {
        super();
        console.log("Login Constructed");
       }
    render() {
        console.log("Login Rendered");
        return <div>
                LOGIN
            </div>
    }
}

The UserStore only gets the view from the page component.

import Pages from '../pages';
class UserStore extends FluxStore {
    constructor() {
        super();
    }
    getView() {
        return Pages.getView();
    }
}
@developit
Copy link
Member

developit commented Jun 1, 2016

Hi @donadley - what versions of preact are you using?

@donadley
Copy link
Author

donadley commented Jun 1, 2016

I am using [email protected] and I am not using preact-router.

@developit
Copy link
Member

One thing that stands out to me is the objectAssign() call: it seems like this is to clone a VNode instance, but it actually creates a plain object, which Preact will ignore when rendering. What is the reason for cloning when storing viewReference?

@donadley
Copy link
Author

donadley commented Jun 1, 2016

The viewReference is used to store the last vNode so that the view doesn't display twice. In this case re-displaying the same vNode. So the viewReference is never displayed.

@developit
Copy link
Member

developit commented Jun 1, 2016

@donadley Hmm - that re-use logic is already handled in Preact, though. Repeated renders of equivalent VNodes is an empty diff. I invited you to the gitter room if you want to chat.

@donadley
Copy link
Author

donadley commented Jun 1, 2016

@developit Thank you, I removed it and it does not reload the previous vNode.

@donadley
Copy link
Author

donadley commented Jun 1, 2016

The objective of the pages component were to change the view. I am bundling the code using browserify and then I am adding the bundle to another web application, kind of like a widget. I first attempted to navigate the views using preact-router, but It seems like I needed to reference the url. This couldn't be done because the views were from a bundle. Do you have any suggestions on how I can accomplish navigating through the view?

@developit
Copy link
Member

developit commented Jun 1, 2016

You can manually specify a URL for preact-router, which makes it ignore the browser's URL. That "url" can actually just be a component name.

However, if you just want to conditionally render components based on a view or some state (like "is logged in"), you could just use conditionals:

class Views extends Component {
  state = {
    view: this.props.view   // can pass default view in as a prop
  };

  // route to a given view
  route = view => {
    this.setState({ view });
  };

  // child components can call:  this.context.route('some-view');
  getChildContext() {
    return { route: this.route };
  }

  render({ children }, { view }) {
    // just render the child whose `name` prop matches the current view:
    return children.filter( child => child.attributes.name===view )[0] || null;
  }
}

// Trick: for logged-out, skip the router/views entirely:
class App extends Component {
  // initialize our state to the auth store's state:
  state = AuthStore.getState();

  onChange = () => {
    this.setState(AuthStore.getState());
  };

  componentDidMount() {
    AuthStore.addChangeListener(this.onChange);
  }

  componentWillUnmount() {
    AuthStore.removeChangeListener(this.onChange);
  }

  render(props, state) {
    // if logged out, always force the login view:
    if (!state.loggedIn) {
      return <Login />;
    }
    return (
      <Views view="home">
        <Home name="home" />
        <Other name="other" />
      </Views>
    );
  }
}

class Home extends Component {
  // switch to the "Other" view
  goOther = () => {
    this.context.route('other');
  };
  render() {
    return (
      <div>
        <a onClick={this.goOther}>Other</a>
      </div>
    );
  }
}

Here's a working demo of the above setup (I just mocked out your Flux store for the demo):
https://jsfiddle.net/developit/k487h3t5/

preview


Edit: It's probably worth noting that you can wrap up the routing stuff into a <Link /> component much like preact-router did/does:

/** <Link to="home">Home</Link> */
const Link = ({ to, ...props }, context) => (
  <a onClick={ () => context.route(to) } {...props} />
);

@donadley
Copy link
Author

donadley commented Jun 2, 2016

Thank you, I think this is exactly what I was looking for. If I wanted to change Components without making someone click, could I just call this.context.route('other'); with "other" being the name of the component? Also I see that your not using constructor in your Component, why is that because I couldn't get it to work without one?

@developit
Copy link
Member

@donadley Yes, that is exactly how to change Components :)

For the constructor - if you don't define a constructor in ES6, super gets automatically called for you. One common hiccup people run into when defining constructors is forgetting to forward the arguments on to the original constructor:

class Foo extends Component {
  constructor(props, context) {
    // forgetting to pass these results in `this.props` and `this.context` being undefined:
    super(props, context);
  }
}

So, in order to avoid having to remember that, I strongly encourage everyone to avoid using constructor whenever possible. Class property initializers are evaluated within the constructor, so that's a great way to avoid having to manually set things up yourself.

For an example, the following two classes are equivalent:

class Foo extends Component {
  constructor(props, context) {
    super(props, context);
    this.state = {
      foo: this.props.foo
    };
  }
}

class Foo {
  state = {
    foo: this.props.foo
  };
}

You can verify this by comparing the resulting transpiled code.

@developit
Copy link
Member

developit commented Jun 2, 2016

I'm going to close this one out since we solved the original question, but please feel free to chat 👍

marvinhagemeister added a commit that referenced this issue Mar 2, 2019
marvinhagemeister added a commit that referenced this issue Mar 15, 2022
Prefer ESM over CJS in package exports
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants