I am currently working on a weather app that changes background based on time of the day and the weather description. I created a state that handles the background style of the page.
class App extends React.Component {
constructor() {
super();
this.state = {
city: undefined,
country: undefined,
localtime: undefined,
timezone: undefined,
temperature: undefined,
precip: undefined,
humidity: undefined,
weather_icon: undefined,
weather_description: "",
error: false,
backgroundStyle: {
backgroundImage: undefined,
backgroundColor: 'black',
backgroundPosition: 'center',
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat'
}
};
}
I also created a function that handles the logic of the background change. In it also is an array holding the images for the background. For example,
const bgImage = [
"",
"'url(img/snowy.jpg)'",
"'url(img/night.jpg)'",
"'url(img/sunny.jpg)'",
"'url(img/stormy.jpg)'",
"'url(img/sunset.jpg)'",
"'url(img/night-rain.jpg)'",
"'url(img/snow-night.jpg)'"
]
// Check for rain
if (description === 'showers' || description.includes('rain')) {
if (time >= 20 || (time >= 0 && time < 7)) {
this.setState({
backgroundStyle: {
backgroundImage: bgImage[6]
}
});
} else if (time > 7 && time < 20) {
this.setState({
backgroundStyle: {
backgroundImage: bgImage[0]
}
});
} else {
this.setState({
backgroundStyle: {
backgroundImage: bgImage[0]
}
});
}
}
In the render function, I return a div and with the style
<div className="App" style={this.state.backgroundStyle}>
The images do not load and I am not exactly sure what I'm doing wrong. Any help please. Thanks.
First of all, I'd highly recommend you to store this kind of static files on a CDN, it will make your client lighter!
But, if you want to use a local image you have two choices.
You can import the image file and use it like any other imported object.
import snowy from '../../snowy.jpg';
import night from '../../night.jpg';
import sunny from '../../sunny.jpg';
import stormy from '../../stormy.jpg';
import sunset from '../../sunset.jpg';
import nightRain from '../../night-rain.jpg';
import snowNight from '../../snow-night.jpg';
const bgImage = [
"",
snowy,
night,
sunny,
stormy,
sunset,
nightRain,
snowNight,
]
or
You need to provide the relative path to the folder, for example: ../../img/snowy.jpg, remember that the relative path refers to the current file in which the path is written.
edit:
You can also use something like:
const bgImg = `url(${this.state.backgroundStyle.backgroundImage})`
Once your url() is under "" Webpack won't be able to find the images later at runtime.
try using:
import logo from './logo.png'; // this will tell webpack that you are using this file, therefore it will be included like logo.<chunk>.png
it would be because you are wrapping the strings in the bgImage array in single and double quotes at the same time
Related
The bounty expires in 5 days. Answers to this question are eligible for a +50 reputation bounty.
andrilla wants to draw more attention to this question:
I would love to see some ideas of what might be causing this and how I might fix it. Ideally I'd love an actual answer, but just some good ideas of what I might try would be super helpful.
#next/font
Uses Next.js with TypeScript and Tailwind CSS
This is my first time using the new #next/font package. I followed Next.js' tutorial, and it was easy to set up. I'm using both Inter and a custom local typeface called App Takeoff. To actually use both of these typefaces, I'm using Tailwind CSS, where Inter is connected to font-sans and App Takeoff is connected to font-display.
Everything works except in one spot
I have done plenty of testing between files, and for some reason both typefaces work everywhere except my Modal component.
Example
index.tsx
modal.tsx via index.tsx
As you can see, the typefaces work just fine when they aren't inside the modal, but as soon as they're in the modal they don't work.
Here's some relevant code:
// app.tsx
import '#/styles/globals.css'
import type { AppProps } from 'next/app'
import { Inter } from '#next/font/google'
const inter = Inter({
subsets: ['latin'],
variable: '--font-inter'
})
import localFont from '#next/font/local'
const appTakeoff = localFont({
src: [
{
path: '../fonts/app-takeoff/regular.otf',
weight: '400',
style: 'normal'
},
{
path: '../fonts/app-takeoff/regular.eot',
weight: '400',
style: 'normal'
},
{
path: '../fonts/app-takeoff/regular.woff2',
weight: '400',
style: 'normal'
},
{
path: '../fonts/app-takeoff/regular.woff',
weight: '400',
style: 'normal'
},
{
path: '../fonts/app-takeoff/regular.ttf',
weight: '400',
style: 'normal'
}
],
variable: '--font-app-takeoff'
})
const App = ({ Component, pageProps }: AppProps) => {
return (
<div className={`${inter.variable} font-sans ${appTakeoff.variable}`}>
<Component {...pageProps} />
</div>
)
}
export default App
// modal.tsx
import type { FunctionComponent } from 'react'
import type { Modal as ModalProps } from '#/typings/components'
import React, { useState } from 'react'
import { Fragment } from 'react'
import { Transition, Dialog } from '#headlessui/react'
const Modal: FunctionComponent<ModalProps> = ({ trigger, place = 'bottom', className, addClass, children }) => {
const [isOpen, setIsOpen] = useState(false),
openModal = () => setIsOpen(true),
closeModal = () => setIsOpen(false)
const Trigger = () => React.cloneElement(trigger, { onClick: openModal })
const enterFrom = place === 'center'
? '-translate-y-[calc(50%-12rem)]'
: 'translate-y-full sm:-translate-y-[calc(50%-12rem)]'
const mainPosition = place === 'center'
? '-translate-y-1/2'
: 'translate-y-0 sm:-translate-y-1/2'
const leaveTo = place === 'center'
? '-translate-y-[calc(50%+8rem)]'
: 'translate-y-full sm:-translate-y-[calc(50%+8rem)]'
return (
<>
<Trigger />
<Dialog open={isOpen} onClose={closeModal} className='z-50'>
{/* Backdrop */}
<div className='fixed inset-0 bg-zinc-200/50 dark:bg-zinc-900/50 backdrop-blur-sm cursor-pointer' aria-hidden='true' />
<Dialog.Panel
className={`
${className || `
fixed left-1/2
${
place === 'center'
? 'top-1/2 rounded-2xl'
: 'bottom-0 sm:bottom-auto sm:top-1/2 rounded-t-2xl xs:rounded-b-2xl'
}
bg-zinc-50 dark:bg-zinc-900
w-min
-translate-x-1/2
overflow-hidden
px-2 xs:px-6
shadow-3xl shadow-primary-400/10
`}
${addClass || ''}
`}
>
{children}
</Dialog.Panel>
<button
onClick={closeModal}
className='
fixed top-4 right-4
bg-primary-600 hover:bg-primary-400
rounded-full
h-7 w-7 desktop:hover:w-20
overflow-x-hidden
transition-[background-color_width] duration-300 ease-in-out
group/button
'
aria-role='button'
>
Close
</button>
</Dialog>
</>
)
}
export default Modal
I hope this information helps. Let me know if there's anything else that would be helpful to know.
Helpful Update
Thank you Jonathan Wieben for explanation of why this isn't working (See Explanation). The issue simply has to do with the scope of the applied styles, and Headless UI's usage of the React Portal component. If anyone has some ideas of how I can either change where the Portal is rendered or change the scope of the styles, that would be super helpful. Jonathan Wieben pointed out a way to do this, however—from my testing—it doesn't work with Tailwind CSS.
The Dialog component you are using renders in a portal (see here).
you typically want to render them as a sibling to the root-most node of your React application. That way you can rely on natural DOM ordering to ensure that their content is rendered on top of your existing application UI.
You can confirm this by inspecting your modal DOM element in your browser and seeing if it is indeed placed outside the div wrapper from your App component (I suspect it is).
If so, this is the explanation for why the modal content does not render with the expected font: It is rendered outside the component that defines the font.
To get around this, you could define your font on a higher level, e.g. in your head like described here: Next docs.
I'm new to programming in Svelte. I would like to be able to use a method on an ES6 class instance in order to dynamically change values being used on my SPA. (Using svelte-spa-router is not an option, unfortunately.)
To get to the crux of the problem in a simplified from:
This is router.js:
import { writable } from 'svelte/store';
export class Router {
constructor(pageMap) {
this.pageMap = pageMap;
this.current = {};
this.currentName = '';
}
get name() {
return this.currentName;
}
set name(pageName) {
this.current = this.pageMap[pageName];
this.currentName = this.current.name;
}
navigate(target) {
this.name = target;
console.log(this.currentName);
}
}
and this is App.js:
<script>
import { Router } from './router';
const pageMap = {
start: {
title: 'start',
name: 'world!',
},
end: {
title: 'end',
name: '-- it works!!!',
},
}
const page = new Router(pageMap);
page.name = 'start';
</script>
<h1>Hello {page.currentName}</h1>
<button on:click={() => page.navigate('end')}>
change to "it works"
</button>
<button on:click={() => page.navigate('start')}>
change back to "world!"
</button>
The desired behavior is that the page.currentName value changes with the button presses. The output to the console on button presses is correct: "--it works!!!", or "world!". However, the text remains "Hello world!", so the value change is not traveling outside the class instance. If I had some way of saying "this = this" upon invoking the navigate method, that would probably solve the problem...
I suspect the correct answer involves writable stores, but I haven't quite been able to figure it out.
I suspect the correct answer involves writable stores
That is correct and trying to use classes like this is not helpful, at least with how Svelte operates right now.
Stores have to be declared at the top level of a component to be usable with $ syntax, putting them inside properties of classes and hiding them behind getters or setters just gets in the way.
I would just use a function that returns an object containing the stores and API you actually need, which then can be destructured right away and used in the markup. E.g.
import { writable, derived } from 'svelte/store';
export function router(pageMap) {
const current = writable({});
const currentName = derived(current, $current => $current.name ?? '');
function navigate(target) {
current.set(pageMap[target]);
}
return {
navigate,
currentName,
};
}
<script>
import { router } from './router';
const pageMap = {
start: {
title: 'start',
name: 'world!',
},
end: {
title: 'end',
name: '-- it works!!!',
},
}
const { navigate, currentName } = router(pageMap);
navigate('start');
</script>
<h1>Hello {$currentName}</h1>
<button on:click={() => navigate('end')}>
change to "it works"
</button>
<button on:click={() => navigate('start')}>
change back to "world!"
</button>
REPL example
You can do something similar with a class, but if you destructure it, the this binding will be broken, so all functions have to be bound manually or you have to pull out the store on its own and keep accessing the functions via the instance.
REPL example
I'm trying to use apexcharts for a next.js application and it's returning me window is not defined.
I would love any help with that.
Does someone know what is happening and why?
import React from 'react';
import Chart from 'react-apexcharts';
export default class Graficos extends React.Component <{}, { options: any, series: any }> {
constructor(props:any) {
super(props);
this.state = {
options: {
chart: {
id: "basic-bar"
},
xaxis: {
categories: [1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999]
}
},
series: [
{
name: "series-1",
data: [30, 40, 45, 50, 49, 60, 70, 91]
}
]
};
}
render() {
return (
<div className="row">
<h1>Gráfico Básico</h1>
<div className="mixed-chart">
<Chart
options={this.state.options}
series={this.state.series}
type="bar"
width={500}
/>
</div>
</div>
);
}
}
One of Next.js's key features is that it can render parts of your React application on the server or even at build time. While this can be helpful in improving your page's performance, the downside is that the server does not provide the all same APIs that your application would have access to in the browser. In this case, there is no global window object defined.
Unfortunately, searching the source code for apexcharts.js turns up many references to window: https://github.com/apexcharts/apexcharts.js/search?q=window. This also occurs in their React wrapper: https://github.com/apexcharts/react-apexcharts/blob/ecf67949df058e15db2bf244e8aa30d78fc8ee47/src/react-apexcharts.jsx#L5. While there doesn't seem to be a way to get apexcharts to avoid references to window, you can prevent Next.js from using the chart on the server. The simplest way to do that is to wrap any reference to the code with a check for whether window is defined, e.g.
<div className="mixed-chart">
{(typeof window !== 'undefined') &&
<Chart
options={this.state.options}
series={this.state.series}
type="bar"
width={500}
/>
}
</div>
With apexcharts, you will also need to do this for the component import because the import alone will trigger a reference to window as shown in that second link. In order to get around that problem you will need to use a dynamic import as opposed to the normal import you currently have: https://nextjs.org/docs/advanced-features/dynamic-import
import dynamic from 'next/dynamic'
const Chart = dynamic(() => import('react-apexcharts'), { ssr: false });
if (typeof window !== "undefined") {
host = window.location.host;
console.log("host--------------", host);
}
I have data from a local JSON file. Each record has some text (string) and a map saved as .gif. The gif files I put in a folder inside ./src.
I need to reference the right map (gif file) to the right record.
Structure of JSON data file:
[
{
"id": "someid",
"text": "some text",
"map": "map name"
},
...
]
I have a component like this:
const Story = ({obj}) => (
<Card className="item" id={obj.id}>
{
obj.map
? (<div className="card-img-none"></div>)
: (<div className="card-img"></div>)
}
<CardBody>
<CardTitle>{obj.text}</CardTitle>
{
obj.map
? (<div className={obj.map}></div>)
: null
}
</CardBody>
</Card>
)
And render the Story component:
import data from './data.json';
...
<div>
{
this.state.data
? (
this.state.data.map((i) => (
<Story obj={i} key={i.id}/>
))
)
: ('Data not found')
}
</div>
Since the maps are gif, I've tried these:
import each gif and set if then statement to check if object.map == gif file name and render the gif file as <img src={mapName}/>, set css img {width: 100%}. This works only for one record since others have map key-value pair.
create a class in index.css for each map (e.g .mapName {background: url('./gif-file.gif') center no-repeat; background-size: cover; Then render the map like the code above. It works for all of them.
However, I run into the issue that I have to set the width and height of the map div to fixed value, setting width: 100% or height: 100% doesn't work. The values turn 0.
It seems like there must be a better solution for this. Any idea?
In case you have the same issue, I've figured out a workaround (it works but I'm not sure it's the best practice):
import data from './data.json'; // Import data from json file
import map from '<relative path>/map.gif'; // Import gif file with relative path
// Inside the class
constructor(props) {
super(props);
this.state = {
data: []
};
}
componentDidMount = () => {
data[3].map = map; // Add value to key 'map' of the target element in data array
this.setState({data});
}
Hope this helps.
I am building my first react app, and an important component for my application is an aesthetic set of radio buttons, that will be used on most of my apps pages, whose values are used to filter data of mine that is being graphed. To make the component simpler for me to make, I decided to use react-bootstrap's ToggleButton and ToggleButtonGroup components, and then create my presentational component ToolButtonGroup as sort of a wrapper around the react-bootstrap components.
What I have so far for ToolButtonGroup.js:
import React, {Component} from 'react';
import { ToggleButton, ToggleButtonGroup } from 'react-bootstrap';
class ToolButtonGroup extends Component {
constructor(props) {
super(props);
}
handleChartTypeChange = (e) => {
this.props.handler();
}
render() {
// Get Variables from the params prop
const { header, buttons, initialVal } = this.props.params;
const { borderRadius, margin, padding, fontsize, border } = this.props.params;
const { gridID, gridColumns, minRowHeight } = this.props.params;
// Create the individual buttons
const pageButtons = buttons.map((buttoninfo, idx) => {
return (
<ToggleButton
key={idx}
style={{
"borderRadius": borderRadius,
"margin": margin,
"padding": padding,
"fontSize": fontsize,
"border": border
}}
bsSize="large"
value={buttoninfo.value}>
{buttoninfo.label}
</ToggleButton>
)
})
// Return the button group
return(
<div className="buttons-container"
style={{"border": "1px solid red";}}
id={gridID}>
<h2 style={{
"width": "100%";
"margin": "0 auto";
"fontSize": "1.75em";
"marginTop": "5px";
"border": "none";
}}
>{header}</h2>
<ToggleButtonGroup
type="radio"
name="charttype-options"
defaultValue={initialVal}
onChange={this.props.handler}
style={{
"display": "grid",
"gridTemplateColumns": "repeat(" + gridColumns + ", 1fr)",
"gridAutoRows": "auto",
"gridGap": "8px"
}}
>
{pageButtons}
</ToggleButtonGroup>
</div>
)
}
}
export default ToolButtonGroup;
I used the parameters passed to this component to make a css grid of ToggleButtons in a ToggleButtonGroup. The data / initial value for the buttons, much of the styling, and also the grid data is all coming in as props from a parent component.
(I'm actually not sure if using the react-bootstrap components made making this component easier or harder for myself...)
Lastly, to demo calling ToolButtonGroup, I have a container component starterContainer.js that creates two tool button groups:
import React, {Component} from 'react';
import ToolButtonGroup from 'ToolButtonGroup';
// Import CSS for this App
import './starterContainer.css';
class StarterApp extends Component {
constructor(props){
super(props);
this.state = {
pitchersOrHitters: "",
position: ""
}
}
// Button and Select Handlers!
handlePitchHitChange = (pitchersOrHitters) => {
this.setState({pitchersOrHitters})
}
handlePositionChange = (position) => {
this.setState({ position: position });
}
render() {
// 0. Load State and Props
const { pitchersOrHitters, position } = this.state;
// Pitcher or Hitter Radio Button Group params
const pitchOrHitButtonGroup = {
borderRadius: "25px",
margin: "1% 10%",
padding: "5%",
fontsize: "2em",
border: "2px solid #BBB",
gridColumns: 1, minRowHeight: "10px", "gridID": "buttons1",
header: "Choose One:",
buttons: [
{ value: "Pitchers", label: "Pitchers" },
{ value: "Hitters", label: "Hitters" },
],
initialVal: "Pitchers"}
// Pitcher or Hitter Radio Button Group params
const positionButtonGroup = {
borderRadius: "10px",
margin: "1% 10%",
padding: "5%",
fontsize: "1.25em",
border: "2px solid #BBB",
gridColumns: 4, minRowHeight: "20px", "gridID": "buttons2",
header: "Choose One:",
buttons: [
{ value: "SP", label: "SP" },
{ value: "RP", label: "RP" },
{ value: "1B", label: "1B" },
{ value: "2B", label: "2B" },
{ value: "SS", label: "SS" },
{ value: "3B", label: "3B" },
{ value: "LF", label: "LF" },
{ value: "RF", label: "RF" },
{ value: "CF", label: "CF" }
],
initialVal: "SP"}
return(
<div className="chart-grid-container">
<ToolButtonGroup
params={pitchOrHitButtonGroup}
value={pitchersOrHitters}
handler={this.handlePitchHitChange} />
<ToolButtonGroup
params={positionButtonGroup}
value={position}
handler={this.handlePositionChange} />
</div>
)
}
}
The only additional is a css grid for the layout of buttongroup and other components. This grid for the staterContainer will have many other components in here as well, including Select widgets and other widget components, as well as components that plot D3 graphs themselves. At first, this starterComponent css grid seems useless, but it will be helpful for my pages later on and I'd like to keep it.
starterComponent.css
.chart-grid-container {
display: grid;
grid-template-columns: repeat(12, 1fr);
grid-auto-rows: minmax(200px, auto);
grid-gap: 5px;
grid-template-areas:
"btns1 btns1 btns2 btns2 btns2 btns2 . . . . . .";
}
#buttons1 { grid-area: btns1; }
#buttons2 { grid-area: btns2; }
With this, I have a few questions / issues:
1. Bug: When my component initially loads, the ToggleButtons are hanging outside of the border designated in the parent container for the CSS grid. However, when I click any of the buttons, the ToggleButtons resize to fit inside of the grid container. I figure this is happening due to some formatting miscommunication between the two components, but I'm not sure how to fix this.
My handleChartTypeChange function isn't doing anything since I'm passing the this.props.handler directly to the ToggleButtonGroup. Can i get rid of handlechartTypeChange? Or should I use it still?
My starterContainer has 2 handle change functions... is there an easy way to turn this into 1 handle change function? This seems duplicative, and I may have more than 2 button groups on a page too.
In general, as a component that will be used in most of my apps and one that will have an important functionality, I want to make sure I'm creating it correctly. Are there any egregious React principles / CSS formatting approaches that
I am passing the initial values to my button groups twice - once in the parameters passed to the ToolButtonGroup component, and once as an initial state in the starterComponent as well. How can i remove this duplication?
Any help is appreciated - this is an important component for me to get correct for a web app of graphing tools that I am creating. If it's functionality is not working correctly, I'm going to have issues elsewhere for sure.
The bulk of your questions might be more for codereview.stack but I have a few thoughts on it.
1.
Not a grid-master yet, but the direct child of the grid (container element with class "chart-grid-container") is an element with class "buttons-container". Right off the bat you have to wonder what if anything is defined under that css class. Second observation, with a glance at this doc page, is that you are missing the grid-area rule and I wonder if "gridID" prop is meant to match up with that.
2.
If you expect additional local logic will be added to that handler later on, yea keep it. Otherwise you don't need it.
3.
I actually like for clarity's sake that each has its own discrete named handler. On the other hand if there are a whole bunch of those components and repeating logic I would probably use es6 Object's dynamic prop names with thunk e.g.:
// pseudo code!
handleChange = (key_name) => {
return (new_value) =>
this.setState({
[key_name]: new_value
})
}
// later ...
handler={this.handleChange('position')} />
4.
For your ToolButtonGroup component, if its not gonna carry its own local state, you should just make it a function in the case that the constructor and handler wrapper are not needed (as discussed above)
A second observation, is it necessary to have the props all passed in under another namespace ("params")? It doesn't seem to serve any purpose. I do think its neat that you are specifying the props for each component instance in its own object but what I would do next is just spread them into the JSX and then reference them directly off this.props as outlined here.
<ToolButtonGroup
{...positionButtonGroupProps}
handler={this.handleChange('position')} />
5.
I am a little unclear on this question because I actually don't spot the value prop being referenced in ToolButtonGroup? Maybe I glossed over it. Anyway I would just attach that to the props object as described above