I need to have a component for handling settings, this component (called Settings) stores state using useState(), for example the primary color.
I need to create a single instance of this component and make it available to every component in the app. Luckily, I already pass down a state dict to every component (I'm very unsure if this is the correct way to achieve that btw), so I can just include this Settings constant.
My problem is that I don't know how to create the component for this purpose, so that I can call its functions and pass it to children.
Here is roughly what my Settings component looks like:
const Settings = (props) => {
const [primaryColor, setPrimaryColor] = useState("")
const getColorTheme = (): string => {
return primaryColor
}
const setColorTheme = (color: string): void => {
setPrimaryColor(color)
}
return null
}
export default Settings
Then I would like to be able to do something like this somewhere else in the app:
const App = () => {
const settings = <Settings />
return (
<div style={{ color: settings.getColorTheme() }}></div>
)
}
Bear in mind that I'm completely new to react, so my approach is probably completely wrong.
You can use a custom Higher Order Component(HOC) for this purpose, which is easier than creating a context(even thougn context is also a HOC). A HOC takes a component and returns a new component. You can send any data from your HOC to the received component.
const withSettings = (Component) => {
const [settings, setSettings] = useState({})
// ...
// ...
<Component {...props} settings={settings}/>
);
And you can use it like this:
const Component = ({ settings }) => {
...your settings UI
}
export default SettingsUI = withSettings(Component);
You can read more about HOCs in the official react documentation
Basically, I'm trying to run a function that creates and adds a Recipe class to an array in React based on an external javascript file that is hosted online - but all the definitions are inside my React app.
The external file looks like (Recipes.js) this:
function LoadRecipes(){
AddToRecipes(new Recipe({
name: "Kronyxium Core",
components: [],
requirements: [],
craftedAt: "Frost Temple Smithy"
}));
}
The way I attempt to go on with this follows:
import React, {useState, useEffect} from 'react';
import RecipeManager from "../logic/RecipeManager.js";
const Recipe = RecipeManager.Recipe;
const recipesList = RecipeManager.recipesList;
const AddToRecipes = RecipeManager.AddToRecipes;
function RecipeController() {
const [loadingRecipes, setLoadingRecipes] = useState(true);
useEffect(() => {
const script = document.createElement("script");
script.src = "https://raw.githack.com/Soralei/extern/main/Recipes.js";
script.async = true;
script.onload = () => {
setLoadingRecipes(false);
}
document.body.appendChild(script);
}, []);
useEffect(() => {
if(!loadingRecipes){
window.LoadRecipes();
}
}, [loadingRecipes]);
return (
<div>
{loadingRecipes ? <p>Loading recipes...</p> : (
<>
<p>Recipes:</p>
{/*recipesList.map((a, index) => <p key={"r"+index}>{a.name}</p>)*/}
</>
)}
</div>
)
}
export default RecipeController
Note that I try to run the function using window.LoadRecipes() once the script has been imported. However, I get undefined errors when the function is run:
Recipes.js:3 Uncaught ReferenceError: AddToRecipes is not defined
at LoadRecipes (Recipes.js:3)
I'm also adding the content of RecipeManager.js for clarity. This is local logic, and the goal is to have the external function make use of it:
class Recipe{
constructor(options = {}){
this.name = options.name || "Unnamed Recipe";
this.components = options.components || [];
this.requirements = options.requirements || [];
this.craftedAt = options.craftedAt || "handcrafted";
}
}
const recipesList = [];
function AddToRecipes(Recipe){
recipesList.push(Recipe);
console.log(Recipe.name, "was added to the recipes list.");
}
const exported = {
Recipe: Recipe,
recipesList: recipesList,
AddToRecipes: AddToRecipes
}
export default exported;
Is this not possible, or am I just doing this entirely wrong?
Why am I doing this? The idea is to host the recipes online in a way that allows for other people to easily view, edit, and have the changes affect my app directly, while keeping most of the work in the React app.
You have to export the function to be able to access it.
Default export (only one per file):
function LoadRecipes(){
AddToRecipes(new Recipe({
name: "Kronyxium Core",
components: [],
requirements: [],
craftedAt: "Frost Temple Smithy"
}));
}
export default LoadRecipes; // export
You should import it like this:
import LoadRecipes from 'pathtofile';
Named export (multiple ones):
export function LoadRecipes() {
AddToRecipes(new Recipe({
name: "Kronyxium Core",
components: [],
requirements: [],
craftedAt: "Frost Temple Smithy"
}));
}
export const add (a, b) => a + b; // another one
Import like this (using { }):
import {
LoadRecipes,
add
} from 'pathtofile';
Named exports are useful to export several values. During the import, one will be able to use the same name to refer to the corresponding value. Concerning the default export, there is only a single default export per module. A default export can be a function, a class, an object or anything else. This value is to be considered as the โmainโ exported value since it will be the simplest to import.
You can read about JavaScript modules here
I am trying to dynamically render components based on their type.
For example:
var type = "Example";
var ComponentName = type + "Component";
return <ComponentName />;
// Returns <examplecomponent /> instead of <ExampleComponent />
I tried the solution proposed here React/JSX dynamic component names
That gave me an error when compiling (using browserify for gulp). It expected XML where I was using an array syntax.
I could solve this by creating a method for every component:
newExampleComponent() {
return <ExampleComponent />;
}
newComponent(type) {
return this["new" + type + "Component"]();
}
But that would mean a new method for every component I create. There must be a more elegant solution to this problem.
I am very open to suggestions.
EDIT:
As pointed out by gmfvpereira these days there is an official documentation entry for this:
https://reactjs.org/docs/jsx-in-depth.html#choosing-the-type-at-runtime
<MyComponent /> compiles to React.createElement(MyComponent, {}), which expects a string (HTML tag) or a function (ReactClass) as first parameter.
You could just store your component class in a variable with a name that starts with an uppercase letter. See HTML tags vs React Components.
var MyComponent = Components[type + "Component"];
return <MyComponent />;
compiles to
var MyComponent = Components[type + "Component"];
return React.createElement(MyComponent, {});
There is an official documentation about how to handle such situations is available here: https://facebook.github.io/react/docs/jsx-in-depth.html#choosing-the-type-at-runtime
Basically it says:
Wrong:
import React from 'react';
import { PhotoStory, VideoStory } from './stories';
const components = {
photo: PhotoStory,
video: VideoStory
};
function Story(props) {
// Wrong! JSX type can't be an expression.
return <components[props.storyType] story={props.story} />;
}
Correct:
import React from 'react';
import { PhotoStory, VideoStory } from './stories';
const components = {
photo: PhotoStory,
video: VideoStory
};
function Story(props) {
// Correct! JSX type can be a capitalized variable.
const SpecificStory = components[props.storyType];
return <SpecificStory story={props.story} />;
}
There should be a container that maps component names to all components that are supposed to be used dynamically. Component classes should be registered in a container because in modular environment there's otherwise no single place where they could be accessed. Component classes cannot be identified by their names without specifying them explicitly because function name is minified in production.
Component map
It can be plain object:
class Foo extends React.Component { ... }
...
const componentsMap = { Foo, Bar };
...
const componentName = 'Fo' + 'o';
const DynamicComponent = componentsMap[componentName];
<DynamicComponent/>;
Or Map instance:
const componentsMap = new Map([[Foo, Foo], [Bar, Bar]]);
...
const DynamicComponent = componentsMap.get(componentName);
Plain object is more suitable because it benefits from property shorthand.
Barrel module
A barrel module with named exports can act as such map:
// Foo.js
export class Foo extends React.Component { ... }
// dynamic-components.js
export * from './Foo';
export * from './Bar';
// some module that uses dynamic component
import * as componentsMap from './dynamic-components';
const componentName = 'Fo' + 'o';
const DynamicComponent = componentsMap[componentName];
<DynamicComponent/>;
This works well with one class per module code style.
Decorator
Decorators can be used with class components for syntactic sugar, this still requires to specify class names explicitly and register them in a map:
const componentsMap = {};
function dynamic(Component) {
if (!Component.displayName)
throw new Error('no name');
componentsMap[Component.displayName] = Component;
return Component;
}
...
#dynamic
class Foo extends React.Component {
static displayName = 'Foo'
...
}
A decorator can be used as higher-order component with functional components:
const Bar = props => ...;
Bar.displayName = 'Bar';
export default dynamic(Bar);
The use of non-standard displayName instead of random property also benefits debugging.
With the introduction of React.lazy, we can now use a true dynamic approach to import the component and render it.
import React, { lazy, Suspense } from 'react';
const App = ({ componentName, ...props }) => {
const DynamicComponent = lazy(() => import(`./${componentName}`));
return (
<Suspense fallback={<div>Loading...</div>}>
<DynamicComponent {...props} />
</Suspense>
);
};
This approach makes some assumptions about the file hierarchy of course and can make the code easy to break.
I figured out a new solution. Do note that I am using ES6 modules so I am requiring the class. You could also define a new React class instead.
var components = {
example: React.createFactory( require('./ExampleComponent') )
};
var type = "example";
newComponent() {
return components[type]({ attribute: "value" });
}
For a wrapper component, a simple solution would be to just use React.createElement directly (using ES6).
import RaisedButton from 'mui/RaisedButton'
import FlatButton from 'mui/FlatButton'
import IconButton from 'mui/IconButton'
class Button extends React.Component {
render() {
const { type, ...props } = this.props
let button = null
switch (type) {
case 'flat': button = FlatButton
break
case 'icon': button = IconButton
break
default: button = RaisedButton
break
}
return (
React.createElement(button, { ...props, disableTouchRipple: true, disableFocusRipple: true })
)
}
}
Across all options with component maps I haven't found the simplest way to define the map using ES6 short syntax:
import React from 'react'
import { PhotoStory, VideoStory } from './stories'
const components = {
PhotoStory,
VideoStory,
}
function Story(props) {
//given that props.story contains 'PhotoStory' or 'VideoStory'
const SpecificStory = components[props.story]
return <SpecificStory/>
}
If your components are global you can simply do:
var nameOfComponent = "SomeComponent";
React.createElement(window[nameOfComponent], {});
Having a map doesn't look good at all with a large amount of components. I'm actually surprised that no one has suggested something like this:
var componentName = "StringThatContainsComponentName";
const importedComponentModule = require("path/to/component/" + componentName).default;
return React.createElement(importedComponentModule);
This one has really helped me when I needed to render a pretty large amount of components loaded in a form of json array.
Assume we have a flag, no different from the state or props:
import ComponentOne from './ComponentOne';
import ComponentTwo from './ComponentTwo';
~~~
const Compo = flag ? ComponentOne : ComponentTwo;
~~~
<Compo someProp={someValue} />
With flag Compo fill with one of ComponentOne or ComponentTwo and then the Compo can act like a React Component.
Assuming you are able to export * from components like so...
// src/components/index.js
export * from './Home'
export * from './Settings'
export * from './SiteList'
You can then re-import * into a new comps object, which can then be used to access your modules.
// src/components/DynamicLoader.js
import React from 'react'
import * as comps from 'components'
export default function ({component, defaultProps}) {
const DynamicComponent = comps[component]
return <DynamicComponent {...defaultProps} />
}
Just pass in a string value that identifies which component you want to paint, wherever you need to paint it.
<DynamicLoader component='Home' defaultProps={someProps} />
Suspose we wish to access various views with dynamic component loading.The following code gives a working example of how to accomplish this by using a string parsed from the search string of a url.
Lets assume we want to access a page 'snozberrys' with two unique views using these url paths:
'http://localhost:3000/snozberrys?aComponent'
and
'http://localhost:3000/snozberrys?bComponent'
we define our view's controller like this:
import React, { Component } from 'react';
import ReactDOM from 'react-dom'
import {
BrowserRouter as Router,
Route
} from 'react-router-dom'
import AComponent from './AComponent.js';
import CoBComponent sole from './BComponent.js';
const views = {
aComponent: <AComponent />,
console: <BComponent />
}
const View = (props) => {
let name = props.location.search.substr(1);
let view = views[name];
if(view == null) throw "View '" + name + "' is undefined";
return view;
}
class ViewManager extends Component {
render() {
return (
<Router>
<div>
<Route path='/' component={View}/>
</div>
</Router>
);
}
}
export default ViewManager
ReactDOM.render(<ViewManager />, document.getElementById('root'));
๐ You can create a reusable component with a fallback component.
export const StringComponent = (Base, { name, Fallback = undefined, ...rest }) => {
const Component = Base[name];
// return fallback if the component doesn't exist
if (!Component) return <Fallback/>
return <Component {...rest}/>;
};
And call it like this:
import * as Pages from "../pages"
const routes = [
{path: "/", element: "Home" },
{path: "/about", element: "About" },
{path: "*", element: "NotFound" },
]
export function App(){
const Fallback = Pages.NotFound
// render each route using a string as name
return (
<div>
{
routes.map(page =>
StringComponent(Pages, { name: page.element, Fallback })
)
}
</div>
)
}
OBS: Imported Pages needs to be something like this:
import Home from "./home"
import About from "./about"
import NotFound from "./not-found"
export { Home, About, NotFound }
I used a bit different Approach, as we always know our actual components so i thought to apply switch case.
Also total no of component were around 7-8 in my case.
getSubComponent(name) {
let customProps = {
"prop1" :"",
"prop2":"",
"prop3":"",
"prop4":""
}
switch (name) {
case "Component1": return <Component1 {...this.props} {...customProps} />
case "Component2": return <Component2 {...this.props} {...customProps} />
case "component3": return <component3 {...this.props} {...customProps} />
}
}
Edit: Other answers are better, see comments.
I solved the same problem this way:
...
render : function () {
var componentToRender = 'component1Name';
var componentLookup = {
component1Name : (<Component1 />),
component2Name : (<Component2 />),
...
};
return (<div>
{componentLookup[componentToRender]}
</div>);
}
...
I'm developing a Mobile Application using React Native. I came up with a thought of, is it possible to use multiple global states using the same Context.
So, I implemented a context like this.
// GlobalStateContext.js
import React, { createContext, useContext, useEffect } from 'react';
const GlobalStateContext = createContext({ globalStateValue: null, setGlobalStateValue: () => {} });
export function useGlobalState (initialValue = null) {
const { globalStateValue, setGlobalStateValue } = useContext(GlobalStateContext);
useEffect(() => { setGlobalStateValue(initialValue) }, []);
return [globalStateValue, setGlobalStateValue];
}
export default function GlobalStateContextProvider (props) {
const { value, children } = props;
return (
<GlobalStateContext.Provider value={value} >
{children}
</GlobalStateContext.Provider>
)
}
In my App.js, I've wrapped my App with the GlobalStateContextProvider.
// App.js
import GlobalStateContextProvider from './path/to/GlobalStateContext';
export default function App() {
const [globalStateValue, setGlobalStateValue] = useState(null);
return (
<GlobalStateContextProvider value={{ globalStateValue, setGlobalStateValue }} >
// <MyAppContent />
</GlobalStateContextProvider>
)
}
Then, I tried to do something like this on some of my screen files.
import { useGlobalState } from '../path/to/GlobalStateContext';
const [myState1, setMyState1] = useGlobalState('State 01');
const [myState2, setMyState2] = useGlobalState('State 02');
But, there are NO TWO SEPARATE global states. I know there should do something more in the implementation if it is possible to achieve what I want.
So, is this thing is possible? When using useState hook, we can do like,
const [myState1, setMyState1] = useState('State 01');
const [myState2, setMyState2] = useState('State 02');
In this case, we have TWO SEPARATE local states.
Like this, I want to know if it is possible to define some GLOBAL STATES and use them.
I have plenty of template components, they resemble each other in way they are used.
Before being rendered to page, template components get wrapped in graphql and connected to redux.
I want to create a HOC to wrap my templates, so that I do not create a new container each time to connect template to data.
Like so:
Here is my page component, where I try to wrap the AppointmentsListTemplate template with gqlList HOC:
import React from 'react'
import { AdminTemplate, AppointmentsListTemplate } from 'components'
import { gqlList } from 'containers'
import {qyListAppointments} from 'services/gqlQueries/Appointments'
const AppointmentsListTemplateWrapped = gqlList(AppointmentsListTemplate, qyListAppointments)
const AdminAppointmentsPage = (props) => {
return (
<AdminTemplate>
<AppointmentsListTemplateWrapped />
</AdminTemplate>
)
}
export default AdminAppointmentsPage
And here is my gqlList HOC:
import React, {Component} from 'react'
import { graphql } from 'react-apollo'
import { connect } from 'react-redux'
import { saveQueryVars } from 'store/helper/actions'
const gqlList = (WrappedComponent, gqlQuery) => {
const GQL = graphql(gqlQuery)(WrappedComponent)
return connect(null, {
saveQueryVars,
})(GQL)
}
export default gqlList
But graphql connector part throws me this error:
Uncaught TypeError: Cannot read property 'displayName' of undefined
at getDisplayName (react-apollo.browser.umd.js:250)
at wrapWithApolloComponent (react-apollo.browser.umd.js:266)
at new eval (gqlList.js:22)
at eval (createClassProxy.js:95)
at instantiate (createClassProxy.js:103)
at Unknown (eval at proxyClass (createClassProxy.js:NaN), :4:17)
at eval (ReactCompositeComponent.js:303)
at measureLifeCyclePerf (ReactCompositeComponent.js:73)
at ReactCompositeComponentWrapper._constructComponentWithoutOwner
What am I doing wrong?
There doesn't seem to be anything wrong with your code, as far as I can tell. I would also rule out redux as the source of the error. But here's what I suggest:
Check your GraphQL query (gqlQuery) so that it gets what it needs and it can return what you need. I suspect it requires some parameters but doesn't get the right type of a parameter - resulting in a Type error.
Here's an example (without redux) of how to pass parameters:
const Data = graphql(fetchData, { options: {variables: {id: this.getId()} }})(Thing);
Here, fetchData requires the id of a Thing and returns data about that thing. Then you can render <Data/>.
You might want to improve your question by adding the query and variables (saveQueryVars) to it. Also, mention the version of react-apollo because that's the module throwing the error. As a side note, the error message coming from the Apollo client is not very helpful.
You can chain them together:
import { changeFoo } from './myReduxActions';
const QUERY = gql`{ FindSomething { Id, Value }}`;
const myComponent = connect(
store => ({
foo: store.fooReducer.foo
}),
dispatch => ({
changeFoo: (val) => dispatch(changeFoo(val))
})
)(graphql(QUERY)(
{props.data.loading && return <div>Loading...</div>}
let myNewFoo = 'abc';
return props.data.FindSomething ?
<div>{`Id is ${props.data.FindSomething.Id}, redux store value foo is ${props.foo}`}
<div onClick={() => props.ChangeFoo(myNewFoo)}></div></div> :
props.data.error ? <div>Error {props.data.error}</div> : null;
));
So you could do connect(graphql(pureComponent))) or written as connect => graphql => component. You can change the order of graphql and connect.