I'm making a fullscreen rotating noticeboard in React with create-react-app.
The App component contains many Screen components, each one containing one child component (named such as WiFiPassword, OpenTimes, Events...) - and the Screen component checks if the current item from App is the name of it's child, and if it is then it shows that screen, if not then it hides it.
This is the code where the values are checked:
class Screen extends Component {
componentDidUpdate() {
const active = this.props.active; // The active item, from parent state
const elemName = this.props.children.type.name; //The name of the child class
...
if(active === elemName){
//If we have a match, then show this screen
}
}
...
}
This works really well when running in development.
When I run npm run build and the process is finished, I load up the app and it doesn't work - it appears that the build process scrubs the class names from elements, which then means nothing is ever shown.
When I console.log() both the active prop and the child class name I get this:
active: WiFiPassword
elemName: t
Whatever screen it is on, elemName, which is the name of Screen's child, is always t
Is there a way to retain the name of a component in react through the build process?
So I found a way around this: instead of relying on the class name, I instead imported every one of my <Screen> elements using a screens/index.js file:
...
export {default as Welcome} from './Welcome'
export {default as WiFi} from './WiFi'
...
Then imported that index, file with each screen available to me:
import * as Screens from './screens'
Then I use whatever screen I want, depending on the same logic from within App:
const Component = Screens[this.state.slide];
<Component/>
It looks like, you should switch to use react-router-dom, because you will have a good way to manage your screens. There is a good component Switch, that shows one component at the time:
import { Switch, Route } from 'react-router'
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
<Route path="/:user" component={User}/>
<Route component={NoMatch}/>
</Switch>
more information here: https://reacttraining.com/react-router/web/api/Switch
Related
I have a main layout for my app and all components are rendered inside it, but this main layout needs to access a variable PageTitle from it's child components, I have looked in react documentation and come up with React Context, but I could'nt figure out how to use it in this case, sorry I am new to React.
Every component rendered by routes has a variable called PageTitle, the MainLayout should use this variable to render current page title on the top of the app, these are all functional components.
<MainLayout>
<Route path='/' exact component={HomePage} />
<Route path='/invoice' exact component={Invoice} />
</MainLayout>
Update:
I can create a context and store the value like this, but I couldn't figure out how to change it in child components.
Also I think this is a bit overkill, and there should a better solution.
export const AppContext = React.createContext({ PageTitle: 'Home' });
<AppContext.Consumer>{value=>value.PageTitle}</AppContext.Consumer>
I've been working on trying to modularize my React.js app (that will be delivered as a Desktop app with Electron) in a way that if I make a new module in the future, I can just add a new folder and modify a couple of files and it should integrate fine.
I got originally inspired by this article: https://www.nylas.com/blog/react-plugins/
After that point, I started doing as much research as I could and ended up creating a JSON file that would live in the server with a manifest of the plugins that are registered for that specific client.
Something like this:
{
"plugins": [
{
"name": "Test Plugin",
"version": "0.0.1",
"path": "testplugin",
"file": "test",
"component":"TestPlugin"
},
{
"name": "Another Plugin",
"version": "0.0.1",
"path": "anothertest",
"file": "othertest",
"component":"TestPluginDeux"
}
]
}
After that, I made a couple folders that match the path value and that contain a component that matches the name in the manifest (e.g. testplugin/test.jsx that exports the TestPlugin component as a default). I also made a pluginStore file that reads the manifest and mounts the plugins in the this.state.
Then, did a ton of research on Google and here and found this answer: React - Dynamically Import Components
With that function, I was able to iterate through the manifest, find the folders in the directory, and mount the plugins in the this.state by running the mountPlugins() function I had created in the pluginStore, inside a componentDidMount() method in my homepage.
So far so good. I'm using React-Router and I was able to mount the plugins dynamically in the State and able to load them in my Home Route by just calling them like this: <TestPlugin />.
The issue that I have now, is that I wanted to dynamically create Routes that would load these components from the state, either by using the component or the render method, but I had no luck. I would always get the same result... Apparently I was passing an object instead of a String.
This was my last iteration at this attempt:
{this.state.modules.registered.map((item) =>
<Route exact path={`/${item.path}`} render={function() {
return <item.component />
}}></Route>
)}
After that, I made a Route that calls a PluginShell component that is called by a Navlink that sends the name of the plugin to inject and load it dynamically.
<Route exact path='/ex/:component' component={PluginShell}></Route>
But I ended having the same exact issue. I'm passing an object and the createElement function expected a string.
I searched all over StackOverflow and found many similar questions with answers. I tried applying all the possible solutions with no luck.
EDIT:
I have put together a GitHub repo that has the minimal set of files to reproduce the issue.
Here's the link:
https://codesandbox.io/embed/aged-moon-nrrjc
Okey pokey. There are a lot of moving parts here that can be vastly simplified.
I'd recommend moving toward a more developer-friendly, opinionated state store (like Redux). I've personally never used Flux, so I can only recommend what I have experience with. As such, you can avoid using plain classes for state management.
You should only import the modules ONCE during the initial application load, then you can dispatch an action to store them to (Redux) state, then share the state as needed with the components (only required if the state is to be shared with many components that are spread across your DOM tree, otherwise, not needed at all).
Module imports are asynchronous, so they can't be loaded immediately. You'll have to set up a condition to wait for the modules to be loaded before mapping them to a Route (in your case, you were trying to map the module's registered string name to the route, instead of the imported module function).
Module imports ideally should be contained to the registered modules within state. In other words, when you import the module, it should just overwrite the module Component string with a Component function. That way, all of the relevant information is placed within one object.
No need to mix and match template literals with string concatenation. Use one or the other.
Use the setState callback to spread any previousState before overwriting it. Much simpler and cleaner looking.
Wrap your import statement within a try/catch block, otherwise, if the module doesn't exist, it may break your application.
Working example (I'm just using React state for this simple example, I also didn't touch any of the other files, which can be simplified as well):
App.js
import React from "react";
import Navigation from "./components/MainNavigation";
import Routes from "./routes";
import { plugins } from "./modules/manifest.json";
import "./assets/css/App.css";
class App extends React.Component {
state = {
importedModules: []
};
componentDidMount = () => {
this.importPlugins();
};
importPlugins = () => {
if (plugins) {
try {
const importedModules = [];
const importPromises = plugins.map(plugin =>
import(`./modules/${plugin.path}/${plugin.file}`).then(module => {
importedModules.push({ ...plugin, Component: module.default });
})
);
Promise.all(importPromises).then(() =>
this.setState(prevState => ({
...prevState,
importedModules
}))
);
} catch (err) {
console.error(err.toString());
}
}
};
render = () => (
<div className="App">
<Navigation />
<Routes {...this.state} />
</div>
);
}
export default App;
routes/index.js
import React from "react";
import React from "react";
import isEmpty from "lodash/isEmpty";
import { Switch, Route } from "react-router-dom";
import ProjectForm from "../modules/core/forms/new-project-form";
import NewPostForm from "../modules/core/forms/new-post-form";
import ProjectLoop from "../modules/core/loops/project-loop";
import Home from "../home";
const Routes = ({ importedModules }) => (
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/projectlist/:filter" component={ProjectLoop} />
<Route exact path="/newproject/:type/:id" component={ProjectForm} />
<Route exact path="/newpost/:type" component={NewPostForm} />
{!isEmpty(importedModules) &&
importedModules.map(({ path, Component }) => (
<Route key={path} exact path={`/${path}`} component={Component} />
))}
</Switch>
);
export default Routes;
You've got the right idea, if anything I guess your syntax is slightly off. I didn't have to tweak much from your example to get dynamic routing work.
Here's a working example of what I think you want to do:
const modules = [{
path: '/',
name: 'Home',
component: Hello
},{
path: '/yo',
name: 'Yo',
component: Yo
}];
function DynamicRoutes() {
return (
<BrowserRouter>
{ modules.map(item => <Route exact path={item.path} component={item.component}/>) }
</BrowserRouter>
);
}
https://stackblitz.com/edit/react-zrdmcq
I think the problem is the way you are trying to render <item.component /> but not sure, did you get the same error whit this?
Try:
<Route exact path={`/${item.path}`} render={function() {
return React.createElement(item.component, props)
}}></Route>
)}
I was building a search engine for custom project.
There I have a search bar from where user can search.
When the user searches, I want the given link to work as it works in case of google
www.google.com/ search? queryRelatedInfo
Notice the search? and then whatever query/parameter/ID
for this I tried something like this in
import React, {Component} from 'react';
import {
BrowserRouter,
Route,
Switch,
Redirect,
} from 'react-router-dom';
import SearchScreen from "./container/searchScreen.js"
import HomeScreen from "./container/home.js";
class route extends Component {
render () {
return (
<BrowserRouter>
<div>
<Switch>
<Route path ="/" exact render ={(props) => <HomeScreen {...props}/>} />
<Route path ="/search?:id" exact render ={(props) => <SearchScreen {...props}/>} />
</Switch>
</div>
</BrowserRouter>
)
}
}
export default route
Notice, <Route path ="/search?:id" above.
Unfortunately this didn't worked out.
I understand that <Route path ="/:id" works but how can i make <Route path ="/search?:id to work i.e how can I make some link like http://localhost:3000/search?9e9e to work
I think this is related with historyApiFallback. That parameter;
(https://webpack.js.org/configuration/dev-server/#devserver-historyapifallback)
When using the HTML5 History API, the index.html page will likely have to be served in place of any 404 responses. devServer.historyApiFallback is disabled by default. Enable it by passing:
module.exports = {
//...
devServer: {
historyApiFallback: true
}
};
Your react app is a single page application. So all path except home path actually is an virtual path, they are not physically exist. The paths must routed to home path. So react-router can manage.
you don't need to put the path like this /search?:id, just put it search
<Route path ="/search" exact render ={(props) => <SearchScreen {...props}/>} />
then inside your SearchScreen component, get the value of search parameter from the URL, check this issue will help.
after the user make search, pass the value like this /search?s=value_here
I am building a list of people my company is working with. Since it is constantly updating with new people I decided to make a single page web app purely in React as a way to learn it since I am new to it and I wanted to learn it for sometime now.
I have index.js and people.js. In people.js I made an object for every person and they each have own atributes (age, location etc.).
Since I don't know how to do it properly I made for each person Home and Single component of it.
Home component is something like:
export class AnnHome extends Component{
render(){
return(
<div>
<Link to={{pathname: 'influencer/Ann',
component: 'AnnSingle'
}}>
<img
src={require('./img/ann.png')}
alt="{Kofs.name}"
className="Avatar rounded-circle"
/></Link>
<p>{Ann.name}</p>
</div>
)
}
}
Before that component I have defined object 'Ann' with it's info.
My question is:
How to make a one Home component and one Single Component like a template so when I go to /ann to fill SingleComponent with Ann info.
Home Component would be like a list of all clients (people):
https://www.w3schools.com/howto/howto_css_team.asp
something like this.
I currently have a lot of Home components that I've put manually.
Hopefully I described my problem, my english is rusty :D
Hopefully this can help get you started.
As I mentioned above, you want to use the React Router to listen to route changes. Then one specifies which component should be mounted for each route. Here I use the route to match on the individual's name (e.g. so /stace matches the element in people where the person's name attribute is Stace.
import React, { Component } from 'react';
import { BrowserRouter, Route, Switch, Link } from 'react-router-dom';
const people = [
{name: 'Stace', img: 'https://placeimg.com/200/200/people'},
{name: 'Marlo', img: 'https://placeimg.com/201/200/people'},
]
const App = props => (
<BrowserRouter>
<div>
<Switch>
<Route path='/stace' component={Person} />
<Route path='/marlo' component={Person} />
</Switch>
{people.map((p, idx) => <Link to={'/' + p.name}>{p.name}</Link>)}
</div>
</BrowserRouter>
)
const Person = props => {
const name = props['match']['path'].toLowerCase().substring(1);
const person = people.filter(p => p.name.toLowerCase() == name)[0]
return (
<div className='person'>
<img src={person.img || ''}></img>
<h2>{person.name || ''}</h2>
</div>
)
}
export default App;
This is of course only meant to demonstrate the router-related concepts. If you use create-react-app, run yarn add react-router#4.3.0 && yarn add react-router-dom#4.3.1, and replace App.js with the content above, you'll be able to preview the app and explore the ideas expressed above. Clicking on a name will change the route to that name and display the data for that name. Below I've clicked on Stace:
I need a help with react-router v2+
I have to change class of navbar when route changed
for example for route /profile className will be "profile-header"
I tried to use this.props.location in navbar component but it shows undefined
Hope your help
Your navbar component (as you described it in your question) is probably not the route component, right? By route component I mean the one that you use in your react-router configuration that is loaded for a specific route.
this.props.location is accessible only on such route component, so you need to pass it down to your navbar.
Let's take an example:
Your router config:
<Router>
<Route path="/" component={App}>
// ...
</Router
Route component App:
class App extends React.Component{
// ...
render() {
return <Navbar location={this.props.location}/>
}
}
There could be a scenario where you may not have access to props.location to pass to the nav component.
Take for example - We had a header component in our project which was included in the routing switch to make it available to all routes.
<Switch>
<Fragment>
<Header/>
<Route path='..' component={...}/>
<Route path='..' component={...}/>
</Fragment>
</Switch>
In the above scenario there is no way to pass the location data to the Header component.
A better solution would be to us the withRouter HOC when a component is not being rendered by your router.
You will still have access to the router properties history, match and location when you wrap it in the withRouter HOC:
import { withRouter } from 'react-router-dom'
....
....
export default withRouter(ThisComponent)
react-router v4
From documentation:
<Route> component property should be used, when you have an existing component. <Route> render property takes an inline function, that returns html.
A <Route> with no path will always match.
Based on this, we can make a <Route> wrapper to any level of your html structure. It will always be displayed and have access to the location object.
As per your request, if a user comes to /profile page, the <header> will have profile-header class name.
<Router>
<Route render={({ location }) =>
<header className={location.pathname.replace(/\//g, '') + '-header'}>
// header content...
</header>
<div id="content"></div>
<footer></footer>
} />
</Router>
I couldn't solve it with the solutions given here and here is what worked for me:
I imported history into my component and assigned history.location.pathname to a variable which I later used for dynamic style manipulation.
In case you are rendering the component with pre-defined location.state values, first set your state with props.location.state then use your state data in your elements.