Get SVG icons dynamically in Next.js - javascript

I wanted to get SVG icons dynamically. I found a method to do this but it seems I made some mistakes. Where am I doing it wrong?
Icon.js
import React from "react";
import { ReactComponent as Bollards } from "./icons/bollards.svg";
import { ReactComponent as Earthquake } from "./icons/earthquake.svg";
import { ReactComponent as Fire } from "./icons/fire.svg";
import { ReactComponent as Healthy } from "./icons/heartbeat.svg";
import { ReactComponent as Home } from "./icons/home.svg";
import { ReactComponent as Planting } from "./icons/planting.svg";
import { ReactComponent as Business } from "./icons/suitcase.svg";
import { ReactComponent as Travel } from "./icons/airplane-around-earth.svg";
const iconTypes = {
bollards: Bollards,
earthQuake: Earthquake,
fire: Fire,
healthy: Healthy,
home: Home,
planting: Planting,
business: Business,
travel: Travel
};
const IconComponent = ({ name, ...props }) => {
let Icon = iconTypes[name];
return <Icon {...props} />;
};
export default IconComponent;
Feautures.js
import React from "react";
import Icon from "./icon";
export default function Features() {
return (
<div>
<Icon name="bollards" />
</div>
);
}
I get this error when trying to export icons.
error - ./components/icon.js
Attempted import error: 'ReactComponent' is not exported from './icons/bollards.svg' (imported as 'Bollards').

You can use SVGR that allows us to import SVGs into your React applications as components.
You need to add #svgr/webpack as a dependency and modify the next.config.js file like this.
next.config.js:
module.exports = {
webpack(config) {
config.module.rules.push({
test: /\.svg$/,
use: ["#svgr/webpack"]
});
return config;
}
};
Then, you can simply import your icons without using ReactComponent.
Icon.js:
import React from "react";
import Bollards from './icons/bollards.svg';
import Earthquake from './icons/earthquake.svg';
import Fire from './icons/fire.svg';
import Healthy from './icons/heartbeat.svg';
import Home from './icons/home.svg';
import Planting from './icons/planting.svg';
import Business from './icons/suitcase.svg';
import Travel from './icons/airplane-around-earth.svg';
const iconTypes = {
bollards: Bollards,
earthQuake: Earthquake,
fire: Fire,
healthy: Healthy,
home: Home,
planting: Planting,
business: Business,
travel: Travel
};
const IconComponent = ({ name, ...props }) => {
let Icon = iconTypes[name];
return <Icon {...props} />;
};
export default IconComponent;
Working demo is available on CodeSandbox.

Related

Storybook 5.2 (CSF) - How to add react-router-dom Link - "You should not use <Link> outside a <Router>"

I am getting an error in my story, because my component is using <Link to="/" />. As far as I can understand the link will have no context in a story so I need to add a decorator, however most articles that describe this are not using the Component Story Format (CSF).
Any ideas how to do this globally or straight into the story?
Error: Invariant failed: You should not use <Link> outside a <Router>
This is my stories file.
// ./stories.tsx.tsx
import React from 'react'
import { LinkComponent } from '.'
import { StoryRouter } from 'storybook-react-router'
export default {
title: 'LinkComponent'
}
export const base = () => {
return <LinkComponent/>
}
This is my component:
// ./LinkComponent.tsx
import React from 'react'
import { Link } from 'react-router-dom'
const LinkComponent = () => {
return (
<section className="links">
<Link to="/">Home</Link>
</section>
)
}
I have tried addDecorator(StoryRouter()); globally in the storybook config as many people suggest, but it breaks the code.
// /.storybook/config.ts
import StoryRouter from 'storybook-react-router';
addDecorator(StoryRouter());
configure(require.context('../src', true, /stories\.tsx$/), module)
Total face-palm moment!!
There is no reason to add a decorator. You can simply import and wrap your component in <MemoryRouter/> to simulate the context of the link.
// ./stories.tsx
import React from 'react'
import { LinkComponent } from '.'
import { MemoryRouter } from 'react-router-dom'
export default {
title: 'LinkComponent'
}
export const base = () => {
return (
<MemoryRouter>
<LinkComponent/>
</MemoryRouter>
}
And... Bob's your uncle(I dont have an uncle called Bob!?!)!

Rendering Component with custom styles with Material UI V1

I am doing something fundamentally wrong leveraging Redux and Material UI V1, and I am looking for help. I have not been able to successfully style the width of my Card component at 75 pixels.
How are we suppose to pass custom styles to our components leveraging withStyles and connect? I have been following an example here in Material UI's docs on the Paper component but have not been able to style my UI successfully. Below are code snippets of my presentational and container component:
Container:
import compose from 'recompose/compose';
import { connect } from 'react-redux';
import { withStyles } from 'material-ui/styles';
import Home from '../components/Home';
import * as homeActions from '../modules/home';
const styles = theme => ({
root: theme.mixins.gutters({
width: 75,
}),
});
const mapStateToProps = (state, ownProps = {}) => {
return {
props: {
classes: styles,
welcomeMessage: state.home.message || ''
}
};
};
const mapDispatchToProps = (dispatch, ownProps = {}) => {
dispatch(homeActions.loadPage());
return {
};
};
export default compose(
withStyles(styles),
connect(mapStateToProps, mapDispatchToProps)
)(Home)
Presentational:
import Card, { CardActions, CardContent, CardMedia } from 'material-ui/Card';
import Button from 'material-ui/Button';
import PropTypes from 'prop-types';
import React from 'react';
import ReactDOM from 'react-dom';
import Typography from 'material-ui/Typography';
import photo from './party.png';
const Component = ({props}) => {
return (
<div>
<Card classes={{
root: props.classes.card
}}>
This is Paper
</Card>
</div>
);
}
Component.propTypes = {
props: PropTypes.object.isRequired
};
export default Component;
EDIT:
I have also leveraged this SO post to see how the Redux and Material UI API for styling have been combined.
Also, I did not explicit state this but according to Material UI, all Paper props are valid for Card props in case you were wondering why I cited Paper documentation.
I also replaced code snippets where previously there were directly links to my project.
I think it is acceptable for your presentational component to express its own style.
Export your presentational component using withStyles:
import Card from 'material-ui/Card';
import { withStyles } from 'material-ui/styles';
import PropTypes from 'prop-types';
import React from 'react';
const styles = theme => ({
root: theme.mixins.gutters({
width: 75,
}),
});
const Component = ({ classes }) => {
return (
<div>
<Card className={classes.root}>This is Paper</Card>
</div>
);
};
Component.propTypes = {
classes: PropTypes.shape({
root: PropTypes.string,
}).isRequired,
};
export default withStyles(styles)(Component);
Then, in your container, you simply connect the presentational component's default export:
export default connect(mapStateToProps, mapDispatchToProps)(Home)

How to access redux variables and functions from deep components

I'm a bit confused about redux implementation.
Let's say my app has this component structure:
-App
--ProfilationStep
---ProfilationStep1
----React-Select (http://jedwatson.github.io/react-select/)
I need to use redux because the app is going to grow bigger and deeper, so I started by setting up Actions, Reducers and Action types for the React-Select component. I also set the mapStateToProps in the App.js file.
Now I need to know how to pass/access the data stored in redux to other components (React-Select for example) and how to edit it with the actions I declared.
This is my index.js file
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import ProfilationSelectReducer from './components/reducers/profilationSelect';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
const store = createStore(
ProfilationSelectReducer
);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>, document.getElementById('root'));
registerServiceWorker();
This is my App.js
import React, { Component } from 'react';
import PropTypes from 'prop-types'
import { bindActionCreators} from 'redux'
import Profilation from './components/Profilation'
import ProfilationStep from './components/Profilation/ProfilationStep'
import { connect } from 'react-redux';
import * as SelectActionCreators from './components/actions/profilationSelect'
import 'react-select/dist/react-select.css';
class App extends Component {
static propTypes = {
steps: PropTypes.array.isRequired
};
render() {
console.log(this.props)
const { dispatch, steps } = this.props;
const changeValue= bindActionCreators(SelectActionCreators.changeValue, dispatch);
const stepComponents = this.props.steps.map((step, index) => (
<ProfilationStep
key={index}
index={index}
step={step}
/>
));
return (
<div className="repower-app">
{ stepComponents }
</div>
);
}
}
const mapStateToProps = state => ({
steps:state.steps
});
export default connect(mapStateToProps)(App);
This is my ProfilationStep.js file
import React, { Component } from 'react';
import PropTypes from 'prop-types'
import ProfilationStep1 from './ProfilationStep1'
import ProfilationStep2 from './ProfilationStep2'
import ProfilationStep3 from './ProfilationStep3'
import ProfilationStep4 from './ProfilationStep4'
import ProfilationStep5 from './ProfilationStep5'
const ProfilationStep = props =>
<div className='ProfilationStep'>
{props.index===0 &&
<ProfilationStep1
step={props.step}
/>
}
{props.stepIndex===2 &&
<ProfilationStep2
handleSelect={props.handleSelect}
handleInput={props.handleInput}
expend={props.expend}
period={props.period}
light={props.light}
gas={props.gas}
/>
}
{props.stepIndex===3 &&
<ProfilationStep3
handleSelect={props.handleSelect}
environment={props.environment}
/>
}
{props.stepIndex===4 &&
<ProfilationStep4
flexibility={props.flexibility}
handleSelect={props.handleSelect}
/>
}
{props.stepIndex===5 &&
<ProfilationStep5
customize={props.customize}
handleSelect={props.handleSelect}
/>
}
</div>
export default ProfilationStep
This is my ProfilationStep1.js file
import React, { Component } from 'react';
import Select from 'react-select';
import PropTypes from 'prop-types'
var jobOptions = [
{ value: 'edilizia', label: 'Edilizia' },
{ value: 'editoria', label: 'Editoria' },
{ value: 'educazione', label: 'Educazione' }
];
const ProfilationStep1 = props =>
<div className='ProfilationStep'>
La mia attività si occupa di <Select
name="job"
value={props.step.job}
onChange={e => props.changeValue(e.target.value)}
options={jobOptions}
/>
</div>
ProfilationStep1.propTypes = {
//isComplete: PropTypes.bool.isRequired,
//isActive: PropTypes.bool.isRequired
job: PropTypes.string.isRequired,
service: PropTypes.string.isRequired,
handleSelect: PropTypes.func.isRequired
}
export default ProfilationStep1
This is my reducer
import * as ProfilationSelectActionTypes from '../actiontypes/profilationSelect';
const initialState = {
steps: [{
job: "",
service: ""
}],
}
export default function ProfilationSelectReducer (state=initialState, action){
switch(action.type){
case ProfilationSelectActionTypes.CHANGE_VALUE:
return {
...state,
steps:[{
job: action.value
}]
};
default:
return state;
}
}
This is my actiontypes file
export const CHANGE_VALUE ='profilationSelect/CHANGE_VALUE';
and, finally, this is my actions file
import * as ProfilationSelectActionTypes from '../actiontypes/profilationSelect';
export const changeValue = value =>{
return{
type: ProfilationSelectActionTypes.CHANGE_VALUE,
value
}
}
Thank you for any help
You are definitely on the right way.
The solution is simple: You bind your state to the react props. With the props, you can do whatever you like (e.g. pass them to react-select). If you want to modify it, you have to map "mapDispatchToProps", where you map functions, which execute your actions to the props. This works the same as "mapStateTopProps":
End of App.js (import your actions file on top, named "profilationSelectActions" here):
const mapStateToProps = state => ({
steps:state.steps
});
const mapDispatchToProps = dispatch => ({
updateJobValue: (value) => dispatch(profilationSelectActions.changeValue(value))
}
// Also add here mapDispatchToProps
export default connect(mapStateToProps, mapDispatchToProps)(App);
Now the function "updateJobValue" is available in the props of your app.js. You can now easily pass it down to your components and to the onChange event of react-select:
In your ProfilationStep1.js change this line:
onChange={e => props.changeValue(e.target.value)}
To this (after you passed the function updateJobValue down)
onChange{e => props.updateJobType(e.target.value)}
After that, updateJobType should go all the way up to App.js and then dispatch the action. After that, the application will re-render with the new steps.

Using custom mocks for components with jest

I'm writing a React-Native application in which I have a screen I need to test:
MyScreen.js
import React, { Component } from "react";
import CustomTable from "./CustomTable";
export default MyScreen extends Component {
render() {
return <CustomTable />;
}
}
CustomTable.ios.js
import React, { Component } from "react";
import { View } from "react-native";
import TableView from "react-native-tableview";
export default MyScreen extends Component {
render() {
return (
<View>
...some stuff
<TableView />
</View>
);
}
}
react-native-tableview calls some iOS specific code so I have mocked it out by simply returning the android version (CustomTable.android.js) in the __mocks__ folder
__mocks__/CustomTable.ios.js
import CustomAndroidTable from "../CustomTable.android";
export const CustomTable = CustomAndroidTable;
What I want to do is test MyScreen.js with Jest, but I want it to use the __mock__/CustomTable.ios. How do I go about getting it to do that? Is that even possible with Jest? My current test file looks like:
tests/MyScreen.test.js
import React from "react";
import renderer from "react-test-renderer";
import MyScreen from "../src/MyScreen";
describe("test", () => {
it("works", () => {
jest.mock("../src/CustomTable.ios");
const tree = renderer.create(
<MyScreen />,
).toJSON();
expect(tree).toMatchSnapshot();
});
But it still calls the original version of CustomTable.ios. What am i doing wrong?
You should call jest.mock outside of your suite test. It should be immediately after your imports.
import React from "react";
import renderer from "react-test-renderer";
import MyScreen from "../src/MyScreen";
jest.mock("../src/CustomTable.ios");
describe("test", () => {
it("works", () => {
const tree = renderer.create(
<MyScreen />,
).toJSON();
expect(tree).toMatchSnapshot();
});

<Provider> does not support changing `store` on the fly

I recently began learning how to use React-Native and Redux together. I got an error in the IOS simulator that I can't figure out how to fix, and I was wondering if anyone had seen this before.
Here's the error:
Provider does not support changing store on the fly. It is most likely that you see this error because you updated to Redux 2.x and React Redux 2.x which no longer hot reload reducers automatically. See https://github.com/reactjs/react-redux/releases/tag/v2.0.0 for the migration instructions.
I followed that link mentioned in the error message, but it seemed like it needed me to be using Webpack. In the link, where it references if(module.hot), I got the following error when trying to use this solution:
Unable to resolve module
So I'm not sure where to go from here. My project so far is very small. I have my index.ios.js, then an app folder containing a components folder, a store folder and a reducer folder. The structure looks like this:
index.ios.js
app
store
index.js
component
index.js
reducer
index.js
Here is my code:
index.ios.js
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';
import {configureStore} from './app/store';
import Main from './app/components/Main';
export default class introToRedux extends Component {
render() {
return (
<Provider store={configureStore()}>
<Main />
</Provider>
);
}
}
AppRegistry.registerComponent('introToRedux', () => introToRedux);
components/Main.js
import React, { Component } from 'react';
import {connect} from 'react-redux';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';
var Main = React.createClass({
render(){
return (
<View>
<Text>{this.props.text}</Text>
</View>
);
}
});
var mapStateToText = (state) => {
return {
text: state.text
}
}
module.exports = connect(mapStateToText)(Main);
reducer/index.js
module.exports = (state={}, action) => {
switch (action.type) {
default:
return state;
}
}
store/index.js
import {createStore} from 'redux';
import reducer from '../reducer';
var defaultState = {
text: "default text"
}
export var configureStore = (initialState=defaultState) => {
return createStore(reducer, initialState);
}
Any help on this would be awesome!
Why do you export configureStore()? You might as well
const initialState = {
text: "default text"
}
export default function reducer (state=initialState, action) {
switch (action.type) {
default:
return state;
}
}
createStore() should be executed once.
index.js
// import stuff
const store = createStore(reducer)
class IntroToRedux extends Component {
render() {
return (
<Provider store={store}>
<Main />
</Provider>
);
}
}
ReactDOM.render(IntroToRedux, document.getElementById('root'))

Categories

Resources