Using React in Web Components + CSS - reactjs

I am trying to implement web components using React. The reason I do this, is because we are a big newspaper company and our 3rd party CMS only allows for extendability using web components. So I read about a way to make a react component a web component on the official docs, like so:
const proto = Object.create(HTMLElement.prototype, {
attachedCallback: {
value: function() {
const mountPoint = document.createElement('span');
this.createShadowRoot().appendChild(mountPoint);
const name = this.getAttribute('name');
const url = 'https://www.google.com/search?q=' + encodeURIComponent(name);
ReactDOM.render(<a href={url}>{name}</a>, mountPoint);
}
}
});
document.registerElement('x-search', {prototype: proto});
This actually does work. I created a react app using create-react-app and rewrote the index.js like so:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
// ReactDOM.render(<App />, document.getElementById('root'));
const proto = Object.create(HTMLElement.prototype, {
attachedCallback: {
value: function() {
const mountPoint = document.createElement('span');
this.createShadowRoot().appendChild(mountPoint);
ReactDOM.render(<App />, mountPoint);
}
}
});
document.registerElement('x-my-app', {prototype: proto});
registerServiceWorker();
And yes—this does actually work! However, what this does, is put my CSS in a separate file and as my web component has shadow DOM it is not styled at all. It needs to live inside the generated web component, but how do I do that?

Related

React js context api is not woking how to fix it?

I'm was making the shopping card Project and I decide to use Contextapi and useReducer for my state management at the start of my project I wrap the App in context Api and it crash my App Here is the Code.
****Main Index *******
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import Context from "./Context";
ReactDOM.render(
<React.StrictMode>
<Context>
<App />
</Context> </React.StrictMode>,
document.getElementById("root")
);
**** Context Api******
import { createContext } from "react";
const Card = createContext();
const Context = ({ childern }) => {
return <Card.Provider>{childern}</Card.Provider>;
};
export default Context;
Just a guess because you didn't provide enough information on the error but you could try moving the context initialization to a separate module and just import it where you need it.
I ran into a strange runtime issue when I initialized it in a UI component.
You need to pass a value prop to context provider without that there is no point of using context API.
As you have mentioned you are using useReducer , an example would be like below to pass the state to the children.
const Context = ({ childern }) => {
const [state,dispatch] = useReducer(reducer,initialState)
return <Card.Provider value={state}>{childern}</Card.Provider>;
};

systemjs to load react function component use hooks run error。

I use webpack to bundle a react function component use useState and set output.libraryTarget: system。
import React, { useState } from "react";
const Foo = () => {
const [str] = useState("workd");
return <div>hello, {str}</div>;
};
export default [Foo];
In other react project I use systemjs to load the above component lik blow
<script type="systemjs-importmap">
{
"imports": {
"baseC": "http://localhost:8000/static/js/bundle.js"
}
}
</script>
<script src="https://cdn.jsdelivr.net/npm/systemjs/dist/system.js"></script>
<script>
System.import("baseC");
</script>
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
window.System.import("baseC").then((res) => {
const Components = res.default[0];
console.log("Components", Components);
ReactDOM.render(
<React.StrictMode>
<Components />
</React.StrictMode>,
document.getElementById("root")
);
});
then I get the error
react.development.js:1476 Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
and the whole demo in https://github.com/ytudt/react-systemjs
what should I do to solve this problem? thanks.

Navigating Using React Router From a Button Outside the React App

I have a simple demo react app that uses react-router-dom (5.2) to show one of 3 "pages".
The app is included on a page that has a button:
index.html:
<button data-app-button data-sku='woo-beanie'>Click Me</button>
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
document.addEventListener('click', function (event) {
if (event.target.closest('button[data-app-button]')) {
// send instructions to react
}
});
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
I want to be able to navigate to a page in the react site, passing through the buttons data-attributes. How is this done with react and react-router ?
UPDATE
#Doppoio's solution works - as long as I'm on a different "page" in my react app. However I have a route like this:
<Route
path="/tryon/:id/:product_sku?">
</Route>
If I start in app from a route of say /faqs and my external button navigates to /tryon/242/jumper-23 my component is awar of the product_sku property.
However when I'm on a page in app of /tryon/242 and then i click an external button to navigate to /tryon/242/jumper-23 the component should be aware of the jumper-23 optional parameter. Currently it isn't.
How do i make the Tryon component detect the change in url of just the optional parameter?
Somewhere in your code under Router, you can add history to window object. And call it from there.
const SomeComponentInsideRouter = () => {
const history = useHistory();
useEffect(() => {
window.reactHistory = history; // Add reference to history via window object.
}, []);
return null;
};
And call it via window.reactHistory
document.addEventListener("click", function (event) {
if (event.target.closest("button[data-app-button]")) {
// send instructions to react
window.reactHistory.push("/about");
}
});
Here's sandbox link
https://codesandbox.io/s/happy-ganguly-b0u2o?file=/src/index.js
Update to mention changed props:
Changes to the props can be detected using componentDidUpdate
https://reactjs.org/docs/react-component.html#componentdidupdate

Unit testing Chakra UI with Jest

Currently I am trying to unit test my application that is built with Create-React-App with typescript, and it is styled with chakraui. Chakrui includes a component ThemeProvider that must wrap the entire application as such.
This is my index.tsx file
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import { ThemeProvider, CSSReset } from "#chakra-ui/core/dist";
import { theme } from "#chakra-ui/core/dist";
ReactDOM.render(
<React.StrictMode>
<ThemeProvider theme={theme}>
<CSSReset />
<App />
</ThemeProvider>
</React.StrictMode>,
document.getElementById("root")
For every unit test that I write, I am having to wrap the component with ThemeProvider for the test to pass:
import React from "react";
import { render } from "#testing-library/react";
import { ThemeProvider } from "#chakra-ui/core/dist";
import App from "./App";
describe("<App />", () => {
test("smoke test", () => {
render(
<ThemeProvider>
<App />
</ThemeProvider>
);
});
});
But this is very verbose, and must be done for every test that I write. Is there a way to do this just once in each .test.tsx file?
You could create your own theme wrapper function
import React from "react";
import { ThemeProvider } from "#chakra-ui/core/dist";
export const ThemeWrapper = ({ children }) => (
<ThemeProvider>{children}</ThemeProvider>
);
And then specify the wrapper in the test
import React from "react";
import { render } from "#testing-library/react";
import { ThemeWrapper } from "../testUtils";
import App from "./App";
describe("<App />", () => {
test("smoke test", () => {
render(<App />, { wrapper: ThemeWrapper });
});
});
This marginally reduces the code for testing. You may be able to also go the route of creating a custom render function (following the steps for redux).
It could look something like
import React from "react";
import { render } from "#testing-library/react";
import { ThemeProvider } from "#chakra-ui/core/dist";
export const renderWithTheme = ui => {
const Wrapper = ({ children }) => (
<ThemeProvider>{children}</ThemeProvider>
);
return render(ui, { wrapper: Wrapper });
};
Basically the same as the wrapper above, but more integrated into a test render function. You can adjust the function signature a bit as well if you need to pass in a theme object, or other render options, this is just a simple example.
Now the test looks like
import React from "react";
import { renderWithTheme } from "../testUtils";
import App from "./App";
describe("<App />", () => {
test("smoke test", () => {
renderWithTheme(<App />);
});
It might be the case that Jest might be mocking your imports from #chakra-ui/core/dist (depending on your jest configuration) which might be resulting your imported chakra-ui components to be undefined.
Importing the Theme Provider and wrapping it everytime with your renders might be one way to do it. The problem might arise when you have multiple components in your index.tsx. So, you might not want to import each and every component.
In that case, you will want to import the actual components from #chakra-ui/core.
The best way (according to me) to do so in Jest is:
jest.mock("#chakra-ui/core", () => {
const ui = jest.requireActual("#chakra-ui/core");
return {
...ui,
customKey: 'customValue',
};
})
This way you can even add custom function and key-values to the imported module.

Can we integrate react component into Aurelia project?

I have created one react component and build it using webpack and deployed on server. I want to integrate this component into Aurelia Project.
I tried using below npm module:
https://www.npmjs.com/package/aurelia-react-loader
In above module mentioned, component name need pass into html file. like in example my-react-component.js is passing into html file.
But my React Component is loading into root in html tag using below code:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render((
<App/>
), document.getElementById('root'));
and after running webpack module, it is creating one JavaScript file that is called index_bundle.js file. Here imported App module is main js component. It is rendering into index.html on root element via ReactDOM.
So I am not sure, How I am going to integrate React component link or url into Aurelia application?
Please let me know if you have any doubts in question. I can do explain in detail.
Thanks in advance.
Harish
Yeah, it's really easy to integrate a react component in to an Aurelia app. Check out my project where I do just that here: https://github.com/ashleygrant/aurelia-loves-react
I'm not even using the loader plugin you mentioned.
Here's how to wrap a third-party react component in an Aurelia custom element:
import React from 'react';
import ReactDOM from 'react-dom';
import ReactDatePicker from 'react-datepicker';
import { noView, bindable, inject } from 'aurelia-framework';
#noView(['react-datepicker/react-datepicker.css'])
#inject(Element)
export class DatePicker {
#bindable selectedDate;
#bindable onChange;
constructor(element) {
this.element = element;
}
selectedDateChanged() {
this.render();
}
render() {
ReactDOM.render(
<ReactDatePicker
selected={this.selectedDate}
onChange={date => this.onChange({ date: date })}
/>,
this.element
);
}
// How to use native DOM events to pass the changed date
/*render() {
ReactDOM.render(
<ReactDatePicker
selected={this.selectedDate}
onChange={date => {
let event = new CustomEvent('change', { 'detail': date });
// Dispatch the event.
this.element.dispatchEvent(event);
}
}
/>,
this.element
);
}*/
}
And here's how to do it while using a custom react component that is part of the Aurelia project:
import React from 'react';
import ReactDOM from 'react-dom';
import { noView, bindable, inject } from 'aurelia-framework';
import FormattedDate from '../react-components/formatted-date';
#noView()
#inject(Element)
export class ReactDate {
#bindable date;
#bindable format = 'dddd, MMMM D, YYYY';
constructor(element) {
this.element = element;
}
dateChanged() {
this.render();
}
formatChanged() {
this.render();
}
render() {
ReactDOM.render(
<FormattedDate
date={this.date}
format={this.format}
/>,
this.element
);
}
}
As you can see, it's pretty simple. I like doing it by hand rather than using the loader as I can set up databinding for each property so it works in a more "Aurelia-ey" way.

Resources