How to use Preact signals with astro to share state - javascript

Problem
When i change the tag value it only changes on the select component but not in the index.astro
I have folder signals where i export signal
export const tagSignal = signal<string>("all");
I use it like this in Select.tsx component, and here evryting changes
import { tagSignal } from "#signal/*";
const setTagValue = (value: string) => {
tagSignal.value = value;
console.log("select", tagSignal.value);
};
export const Select = () => {
const [display, setDisplay] = useState(false);
const [selectedName, setSelectedName] = useState("all"); // this will be change to only signals still under refator
setTagValue(selectedName);
-------
------
but when I import it to index.astro like this I get only "all" value witch is inital value
---
import { Icon } from "astro-icon";
import { Picture } from "astro-imagetools/components";
import Layout from "#layouts/Layout.astro";
import { Select } from "#components/Select";
import Card from "#components/Card.astro";
import { getCollection } from "astro:content";
import { getProjectsByTag } from "#utils/*";
import { tagSignal } from "#signal/*";
const projects = await getCollection("projects");
const filteredProjects = getProjectsByTag(projects, tagSignal.value);
// TODO: add links
console.log("index", tagSignal.value);
---
/// some code here
<section id="projects" class="projects">
<Select client:only="preact" />
<div class="projects-wrapper">
{
filteredProjects.map(({ data: { title, heroImage } }) => (
<Card name={title} bg_path={heroImage} />
))
}
</div>
</section>
---

I see two issues here.
You are depending on dynamic JS variables in an .astro file. It doesn't work the way you are expecting—all the javascript in .astro files, with the exception of the "islands," e.g., your Select.tsx component, is being evaluated when the page is being built. So Astro grabs the initial value of tagSignal, but makes it a static string.
People can get bitten by, e.g., the trivial ©2010—{new Date().getFullYear()} in the footer – it won't magically update on the new year's eve if used in .astro file.
The state (signal's current value) is not shared accross the islands. If you want to share it, you need either a global state solution (I haven't used it in Astro yet), or just create a common parent for the Select and the filtering logic, e.g.:
{/* the signal will be declared (or imported) in `DynamicSection`*/}
<DynamicSection client:only="preact">
<Select />
<div class="projects-wrapper">
{
filteredProjects.map(({ data: { title, heroImage } }) => (
<Card name={title} bg_path={heroImage} />
))
}
</div>
</ DynamicSection>
(The simplest global state solution would be probably using the url with a query string, and derive the state from its value).

Related

Not able to render images in Gatsby from .yml file [duplicate]

I'm trying to display couple of images from array of objects containing images urls. I'm aware that StaticImage has to accept local variables as props values. These two variables for image urls are both local.
Why is this not working and is there a workaround?
import React from 'react';
import { StaticImage } from 'gatsby-plugin-image';
const TestPage = (props) => {
const itemData = [
{
img: 'https://images.unsplash.com/photo-1549388604-817d15aa0110?w=161&fit=crop',
title: 'Bed',
},
{
img: 'https://images.unsplash.com/photo-1563298723-dcfebaa392e3?w=161&fit=crop',
title: 'Kitchen',
},
];
const testSrc = 'https://images.unsplash.com/photo-1523413651479-597eb2da0ad6?w=161&fit=crop';
const testSrc2 = itemData[0].img;
return (
<>
<StaticImage src={testSrc} alt="works" />
<StaticImage src={testSrc2} alt="doesn't" />
</>
)
}
export default TestPage;
As you said, there's a restriction in the component:
Restrictions on using StaticImage
The images are loaded and processed at build time, so there are
restrictions on how you pass props to the component. The values need
to be statically-analyzed at build time, which means you can’t pass
them as props from outside the component, or use the results of
function calls, for example. You can either use static values, or
variables within the component’s local scope. See the following
examples:
In your case, a plain assignation works but an object assignation doesn't. Change it to:
import React from 'react';
import { StaticImage } from 'gatsby-plugin-image';
const TestPage = (props) => {
const testSrc = 'https://images.unsplash.com/photo-1523413651479-597eb2da0ad6?w=161&fit=crop';
const testSrc2 = 'https://images.unsplash.com/photo-1563298723-dcfebaa392e3?w=161&fit=crop';
return (
<>
<StaticImage src={testSrc} alt="Bed" />
<StaticImage src={testSrc2} alt="Kitchen" />
</>
)
}
export default TestPage;

Creating a slate.js editor component that keeps its state in markdown

I want to create a slate.js-based editor component that keeps it's state in markdown. Slate.js docs keep repeating how simple serializing and deserializing state into md should be, but they don't provide an actual way to do it.
I tried achieving such editor with remark-slate-transformer in a very straight-forward way , based on these two examples: remark-slate-transformer, slate:
import React, { useMemo, useState } from "react";
import { createEditor } from "slate";
import { Slate, Editable, withReact } from "slate-react";
import stringify from "remark-stringify";
import unified from "unified";
import markdownParser from "remark-parse";
import { remarkToSlate, slateToRemark } from "remark-slate-transformer";
import { withHistory } from "slate-history";
function markdown2slate(markdown) {
const processor = unified().use(markdownParser).use(remarkToSlate);
return processor.processSync(markdown).result;
}
function slate2markdown(slate) {
const processor = unified().use(slateToRemark).use(stringify);
const ast = processor.runSync({ type: "root", children: slate });
return processor.stringify(ast);
}
export const App = () => {
const editor = useMemo(() => withHistory(withReact(createEditor())), []);
const [value, setValue] = useState("**initialText**");
const onChange = (newVal) => {
setValue(slate2markdown(newVal));
};
const editorValue = markdown2slate(value);
return (
<div className="wrapper">
<Slate editor={editor} value={editorValue} onChange={onChange}>
<Editable />
</Slate>
</div>
);
};
export default App;
sandbox here
But this doesn't work very well. I expect the initial text to appear in bold, but it doesn't. The cursor keeps jumping back to position 0 on every keystroke. Also, when I delete the string (value becomes ''), the editor breaks.
What is the correct, hassle-free way of making an editor component with state stored as markdown?
I'm not sure why you would absolutely want to store the editor state as Markdown, but this just cannot and will not work: you can't simply swap Slate internal state to something different than what it expects, its own object model, and expect it to work.
What you can do is to deserialize Markdown content into a Slate state object, feed that to the editor, let Slate do its thing while you edit and then serialize back to Markdown to do whatever you need to do with it, store it, send it, etc.

Override the constant file values in React

constant file -> constant.js
export default {
CITY: 'Banglore',
STATE: 'Karnataka'
}
Show Default City Name -> address.jsx
import React from "react";
import CONSTANTS from "./constants";
import "./styles.css";
const Address = () => {
return (
<div className="App">
<p> City : {`${CONSTANTS.CITY}`} </p>
<p> State : {`${CONSTANTS.STATE}`} </p>
</div>
);
};
export default Address;
expected output:
city: banglore
state: karnataka
we are importing the constant values from constant.js file, now the problem is we have to make one API call which may return overriding values for the constant keys
example of API response:
{
CITY: 'Mysuru'
}
then CITY is constant file should override with the new value which come after API response and rest other keys should keep their values same.
expected output:
city: Mysuru
state: karnataka
this the basic problem case for me, actually our application already in mid phase of development and more than 500+ constant keys are imported in 100+ components.
1. we are using redux in our application
2. we have to call API only once that should effects to all the components
what is the best way to achieve this problem, how can i override my constant files once i make the call to backend, Thank you
Since the question has changed, so does my answer (keeping the original one below). I'd suggest to rebuild the constants file to either return the constants or from Localstorage. However, be aware that the current components will not be rebuild using this approach. Only thing that'll trigger a rebuild is either use Redux for this or local state management.
const data = {
CITY: 'Banglore',
STATE: 'Karnataka'
}
const getData = () => {
let localData = window.localStorage.getItem('const-data');
if (!localData) {
axios.get('url')
.then(response => {
localData = {...response.data};
window.localStorage.setItem('const-data', JSON.stringify({...localData}));
});
}
return localData ? localData : data;
}
export default getData();
Original answer:
This is how I'd solve it using local state. It was some time ago since I was using Redux. Though the same principle should apply instead of putting the data in local state, put it in the Redux.
I prefer the simplicity of using local state whenever there's no need to share data over multiple components.
import React, { useEffect } from "react";
import CONSTANTS from "./constants";
import "./styles.css";
const Address = () => {
const [constants, setConstants] = useState({...CONSTANTS});
useEffect(() => {
//api call
//setConstants({...apiData});
}, []);
return (
<div className="App">
<p> City : {`${constants.CITY}`} </p>
<p> State : {`${constants.STATE}`} </p>
</div>
);
};
export default Address;

how to choose different json file to loop?

I am new to react. I write a script to loop a json file(../a.json) to show cities on screen. Inside a.json, it's a list of city information in UK.
Such as :
["Avon",
"Bedfordshire"
...
...
"Tyrone"
]
The code will loop this a json file and show each city in the screen.
Now I want to add a param to let people choose which country to show. I add another 4 json file,
import b from "../b.json";
import d from "../d.json";
import e from "../e.json";
import f from "../f.json";
I also created enum type Locale, but I don't know how to adjust code in code file 3. Would you help take a look?
Can I create a Map in code file 3, and based on what locale passed from Home, I can provide json file. Does it work? How should I adjust code?
Code file 1
Code file 3
You can do something like the below code. Add all the locals in an object. Then onChange of select update the localData.
import React from "react";
import ES from "../data/ES.json";
import IT from "../data/IT.json";
import UK from "../data/UK.json";
const locals = {
UK: UK,
ES: ES,
IT: IT
};
class App extends Component {
constructor() {
super();
this.state = {
index: 0,
selectedLocal: "UK",
localData: locals["UK"]
};
}
onChange = e => {
this.setState({
index: 0,
localData: locals[e.target.value],
selectedLocal: e.target.value
});
};
render() {
return (
<div>
<select value={this.state.selectedLocal} onChange={this.onChange}>
{Object.keys(locals).map(local => {
return <option value={local}>{local}</option>;
})}
</select>
<div className="hints-on-home-screen">
<div
style={{
fontSize: this.props.testFontSize
}}
>
{this.state.localData[this.state.index]}
</div>
</div>
</div>
);
}
}
In your Home component, create a select with all countries as options. Maintain a state and update it upon onChange and pass it on to your Test component.
Home Component
import * as React from "react";
import { Locale } from "../component/testLocale";
type Props = {
testLocale: Locale.UK
}
export class Home extends React.Component<Props, any> {
state = {
testLocale: Locale.UK
}
handleChange = (e) => {
this.setState({testLocale: e.target.value})
}
render() {
return (
<div className="home-background">
<select onChange={this.handleChange} value={this.state.testLocale}>
{Object.keys(Locale).map(country => <option value={Locale[country]}>{country}</option>)}
</select>
<Test testLocale={this.state.testLocale}/>
</div>
);
}
}
what you want to do is add some way to get the chosen json from the user, a Picker (its a dropdown list) is a good choice for this one, feed it the enum lists you have, and make a state called local or something, you'll use this state to handle the selected local, so you'll have something like this for your Picker onValueChange
onValueChange={(itemValue, itemIndex) => setSelectedLocal(itemValue)}
Note that react native uses the hook api in your case, you do something like:
onValueChange={(itemValue, itemIndex) => this.setState({locale: itemValue})}
and use local as a prop for Test
<Test testLocale={this.props.local}/>
See this solution,
Pay careful attention to how everything is configured.
This is a common strategy to manipulate elements using state and data structures.
Stackblitz Example Solution

Data from Gatsby GraphQL always returns undefined?

Gatsby noob here so please bear with me. I have a component that accepts props from the index.js where it is supposed to receive data from an array of objects but will always receive the error TypeError: Cannot read property 'map' of undefined where it's referring to the Hero.js component index.js is calling for.
My assumption is that the data being queried in index.js is either not specific enough or that it is rendering the component before data is received. Here is the index.js file:
import { graphql } from 'gatsby';
import { Layout, SEO, Hero } from 'components';
const IndexPage = ({ data }) => {
const dataFetch = data.contentfulTemplateIndex.heroes;
let tester = () => {
for (let count = 0; count < dataFetch.length; count++) {
return <Hero {...props} />;
}
};
console.log(dataFetch);
let props = {
impactText: dataFetch.impactText,
labels: dataFetch.labels,
primaryLabel: dataFetch.primaryLabel,
location: dataFetch.location
// supportingText: dataFetch.supportingText.json
};
return (
<Layout>
{dataFetch && tester()}
</Layout>
);
};
export const query = graphql`
query {
contentfulTemplateIndex {
heroes {
image {
fluid {
src
}
}
impactText
labels
location
primaryLabel
supportingText {
json
}
}
}
}
`;
export default IndexPage;
Here is the Hero.js component which index.js is calling:
import { Link } from 'gatsby';
import { documentToReactComponents } from '#contentful/rich-text-react-renderer';
import cx from 'classnames';
import styles from './hero.module.scss';
const Hero = (props) => {
return (
<div>
<ul>
<Link className={styles.pills}>{props.primaryLabel}</Link>
{props.labels.map((label) => {
return <Link className={styles.pills}>{label}</Link>;
})}
</ul>
<div className={styles.grid}>
<h1>{props.impactText}</h1>
</div>
</div>
);
};
export default Hero;
It's impossible for an outsider to debug your code without a minimum reproducable example.
The best way to debug GraphQL is to use the GraphiQL interface of your browser.
Run gatsby develop. If it fails because of the TypeError remove the lines of code that cause the type error (but not the code of your GraphQL query!). You need to get your development server runnning.
Open your browser, use the URL: http://localhost:8000/___graphql
Copy your graphQL query from your code and paste it into the GraphiQL query window.
Can you access your data there? If not you made a mistake writing your query or the data is not where it's supposed to be.
This way you can make sure the data exists.
It also helps to console.log(props) so you can examine the data object:
const Hero = (props) => {
console.log(props);
return (

Categories